Skip to content

Commit 16a718b

Browse files
authored
Update internal components and examples (#14)
* Re-implement actions, services * Add tests * Update examples
1 parent 01479e8 commit 16a718b

25 files changed

+733
-426
lines changed

.vscode/settings.json

+20-14
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
{
2-
"python.pythonPath": "~/locus_dev/devel/share/aiorospy/venv/bin/python",
3-
"python.autoComplete.extraPaths": [
4-
"${workspaceFolder}"
5-
],
6-
"python.testing.unittestArgs": [
7-
"-v",
8-
"-s",
9-
"./aiorospy/tests",
10-
"-p",
11-
"test*.py"
12-
],
13-
"python.testing.pyTestEnabled": false,
14-
"python.testing.nosetestsEnabled": false,
15-
"python.testing.unittestEnabled": true
2+
"python.pythonPath": "~/locus_dev/devel/share/aiorospy/venv/bin/python",
3+
"python.autoComplete.extraPaths": [
4+
"${workspaceFolder}"
5+
],
6+
"python.testing.unittestArgs": [
7+
"-v",
8+
"-s",
9+
"./aiorospy/tests",
10+
"-p",
11+
"test*.py"
12+
],
13+
"python.testing.pyTestEnabled": false,
14+
"python.testing.nosetestsEnabled": false,
15+
"python.testing.unittestEnabled": true,
16+
"editor.formatOnSave": true,
17+
"[python]": {
18+
"editor.codeActionsOnSave": {
19+
"source.organizeImports": true
20+
}
21+
},
1622
}

README.md

+18-88
Original file line numberDiff line numberDiff line change
@@ -26,104 +26,34 @@ Simplifies dependency management and makes using a different version of python p
2626

2727
## Examples
2828

29-
Check out the scripts folder for a few `rosrun`able scripts.
29+
Check out the `scripts` folder for examples of topics, services, actions, and a composite node.
3030

31-
### Example Publisher and Subscriber
31+
Take note that when using` rospy` and `asyncio` together, the following boilerplate is recommended:
3232

33-
```python
34-
#!/usr/bin/env python3.6
33+
```
34+
import aiorospy
3535
import asyncio
3636
import rospy
37-
from aiorospy import AsyncSubscriber
38-
from std_msgs.msg import String
39-
40-
sub = AsyncSubscriber('ping', String)
41-
pub = rospy.Publisher('ping', String, queue_size=1)
42-
43-
async def send_ping():
44-
while True:
45-
pub.publish(f'Ping!')
46-
await asyncio.sleep(1)
47-
48-
async def receive_ping():
49-
async for message in sub.subscribe():
50-
print(f'Received: {message.data}')
51-
52-
if __name__ == '__main__':
53-
rospy.init_node('example_pubsub', disable_signals=True)
54-
try:
55-
asyncio.get_event_loop().run_until_complete(asyncio.gather(send_ping(), receive_ping())
56-
finally:
57-
rospy.signal_shutdown('Shutting down.')
58-
```
5937
60-
### Example Service Proxy
38+
rospy.init_node('node_name')
6139
62-
```python
63-
#!/usr/bin/env python3.6
64-
import asyncio
65-
import rospy
66-
from aiorospy import AsyncServiceProxy
67-
from std_srvs.srv import SetBool, SetBoolResponse
68-
69-
proxy = AsyncServiceProxy('ping', SetBool)
70-
71-
async def call_service():
72-
while True:
73-
response = await proxy.send(True)
74-
print(response.message)
75-
await asyncio.sleep(1)
76-
77-
if __name__ == '__main__':
78-
rospy.init_node('example_service', disable_signals=True)
79-
try:
80-
asyncio.get_event_loop().run_until_complete(call_service())
81-
finally:
82-
rospy.signal_shutdown('Shutting down.')
83-
```
40+
loop = asyncio.get_event_loop()
8441
42+
tasks = asyncio.gather(
43+
my_top_level_task(),
44+
# ...
45+
)
8546
86-
### Example Simple Action
47+
# Any uncaught exceptions will cause this task to get cancelled
48+
aiorospy.cancel_on_exception(tasks)
49+
# ROS shutdown will cause this task toget cancelled
50+
aiorospy.cancel_on_shutdown(tasks)
8751
88-
```python
89-
#!/usr/bin/env python3.6
90-
import asyncio
91-
import rospy
92-
from actionlib.msg import TwoIntsAction, TwoIntsGoal, TwoIntsResult
93-
from aiorospy import AsyncSimpleActionClient, AsyncSimpleActionServer
94-
95-
class ExampleSimpleAction:
96-
97-
def __init__(self):
98-
self.server = AsyncSimpleActionServer('add_two_ints', TwoIntsAction, self.execute)
99-
self.server.start()
100-
101-
self.client = AsyncSimpleActionClient('add_two_ints', TwoIntsAction)
102-
103-
async def execute(self, goal):
104-
await asyncio.sleep(3)
105-
self.server.set_succeeded(TwoIntsResult(goal.a + goal.b))
106-
107-
async def send_goals(self):
108-
while True:
109-
result = await self.client.send_goal(TwoIntsGoal(1, 2))
110-
print(f'The result is: {result}')
111-
await asyncio.sleep(5)
112-
113-
if __name__ == '__main__':
114-
rospy.init_node('example_actionlib', disable_signals=True)
115-
example = ExampleSimpleAction()
116-
try:
117-
asyncio.get_event_loop().run_until_complete(example.send_goals())
118-
finally:
119-
rospy.signal_shutdown('Shutting down.')
52+
try:
53+
loop.run_until_complete(tasks)
54+
except asyncio.CancelledError:
55+
pass
12056
```
12157

12258
## How it works
12359
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`.
124-
125-
Note the use of `disable_signals=True` and `rospy.signal_shutdown()` in the examples. This is necessary to give asyncio control of shutting down the event loop and application in general. Alternatively you can manually call `loop.stop()` and do cleanup yourself.
126-
127-
## TODO
128-
- Implement the non-Simple ActionClient and ActionServer
129-
- Explore need for `queue_size` to be handled.

aiorospy/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ if(CATKIN_ENABLE_TESTING)
2121
set(python_test_scripts
2222
tests/test_action_client.py
2323
tests/test_action_server.py
24+
tests/test_service_proxy.py
25+
tests/test_service.py
2426
tests/test_subscriber.py
2527
)
2628

aiorospy/package.xml

+13-8
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
1-
<?xml version="1.0"?>
1+
<?xml version="1.0" ?>
22
<package format="2">
33
<name>aiorospy</name>
44
<version>0.0.0</version>
55
<description>Asyncio wrapper around rospy I/O interfaces.</description>
6-
6+
7+
<author email="[email protected]">Andrew Blakey</author>
8+
<author email="[email protected]">Paul Bovbel</author>
9+
710
<maintainer email="[email protected]">Andrew Blakey</maintainer>
11+
<maintainer email="[email protected]">Paul Bovbel</maintainer>
12+
813
<license>MIT</license>
9-
14+
1015
<buildtool_depend>catkin</buildtool_depend>
11-
16+
1217
<exec_depend>actionlib</exec_depend>
1318
<exec_depend>rospy</exec_depend>
14-
19+
1520
<test_depend>catkin_virtualenv</test_depend>
16-
21+
<test_depend>python3.7</test_depend>
1722
<test_depend>roslint</test_depend>
1823
<test_depend>rostest</test_depend>
19-
24+
2025
<export>
2126
<pip_requirements>requirements.txt</pip_requirements>
2227
</export>
23-
28+
2429
</package>

aiorospy/requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
aiohttp==3.5.4
21
aiostream==0.3.1
2+
aiounittest==1.2.1
33
janus==0.4.0

aiorospy/src/aiorospy/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .action import AsyncActionClient, AsyncActionServer
2+
from .helpers import ExceptionMonitor, cancel_on_exception, cancel_on_shutdown
23
from .service import AsyncService, AsyncServiceProxy
3-
from .topic import AsyncSubscriber, AsyncPublisher
4+
from .topic import AsyncPublisher, AsyncSubscriber
45

56
__all__ = ('AsyncSubscriber', 'AsyncService', 'AsyncServiceProxy', 'AsyncActionClient', 'AsyncActionServer')

0 commit comments

Comments
 (0)