Skip to content

Commit 6ed5f20

Browse files
committed
update docs and add tests
1 parent 97e8d5f commit 6ed5f20

File tree

7 files changed

+116
-8
lines changed

7 files changed

+116
-8
lines changed

docs/reference/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ API Reference
88
from roslibpy import *
99

1010
.. automodule:: roslibpy
11-
.. automodule:: roslibpy.actionlib
1211
.. automodule:: roslibpy.tf
12+
.. automodule:: roslibpy.ros1.actionlib
1313
.. automodule:: roslibpy.ros2

src/roslibpy/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ class and are passed around via :class:`Topics <Topic>` using a **publish/subscr
8282
.. autoclass:: ServiceResponse
8383
:members:
8484
85-
Actions
86-
--------
85+
Actions (ROS 2)
86+
---------------
8787
8888
An Action client for ROS 2 Actions can be used by managing goal/feedback/result
8989
messages via :class:`ActionClient <ActionClient>`.

src/roslibpy/comm/comm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ def _handle_action_result(self, message):
174174

175175
LOGGER.debug("Received Action result with status: %s", status)
176176

177-
results = {"status": ActionGoalStatus(status).name, "values": message.get("values", {})}
177+
results = {"status": ActionGoalStatus(status), "values": message.get("values", {})}
178178

179179
if "result" in message and message["result"] is False:
180180
if errback:

src/roslibpy/core.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import logging
55
import time
6+
import threading
67
from enum import Enum
78

89
# Python 2/3 compatibility import list
@@ -553,6 +554,7 @@ def __init__(self, ros, name, action_type, reconnect_on_close=True):
553554
self._service_callback = None
554555
self._is_advertised = False
555556
self.reconnect_on_close = reconnect_on_close
557+
self.wait_results = {}
556558

557559
def send_goal(self, goal, resultback, feedback, errback):
558560
"""Start a call to an action.
@@ -572,6 +574,7 @@ def send_goal(self, goal, resultback, feedback, errback):
572574
return
573575

574576
action_goal_id = "send_action_goal:%s:%d" % (self.name, self.ros.id_counter)
577+
self.wait_results[action_goal_id] = threading.Event()
575578

576579
message = Message(
577580
{
@@ -584,7 +587,19 @@ def send_goal(self, goal, resultback, feedback, errback):
584587
}
585588
)
586589

587-
self.ros.call_async_action(message, resultback, feedback, errback)
590+
def _internal_resultback(result):
591+
wait_result_event = self.wait_results.get(action_goal_id)
592+
if wait_result_event is not None:
593+
wait_result_event.set()
594+
resultback(result)
595+
596+
def _internal_errback(err):
597+
wait_result_event = self.wait_results.get(action_goal_id)
598+
if wait_result_event is not None:
599+
wait_result_event.set()
600+
errback(err)
601+
602+
self.ros.call_async_action(message, _internal_resultback, feedback, _internal_errback)
588603
return action_goal_id
589604

590605
def cancel_goal(self, goal_id):
@@ -603,9 +618,28 @@ def cancel_goal(self, goal_id):
603618
}
604619
)
605620
self.ros.send_on_ready(message)
621+
606622
# Remove message_id from RosBridgeProtocol._pending_action_requests in comms.py?
607623
# Not needed since an action result is returned upon cancelation.
608624

625+
def wait_goal(self, goal_id, timeout=None):
626+
"""Block until the result is available.
627+
628+
If ``timeout`` is ``None``, it will wait indefinitely.
629+
630+
Args:
631+
goal_id: Goal ID returned from ``send_goal()``
632+
timeout (:obj:`int`): Timeout to wait for the result expressed in seconds.
633+
"""
634+
635+
wait_result_event = self.wait_results.get(goal_id)
636+
if wait_result_event is None:
637+
raise ValueError("Unknown goal ID")
638+
639+
if not wait_result_event.wait(timeout):
640+
raise RosTimeoutError("Goal failed to receive result")
641+
642+
self.wait_results.pop(goal_id, None)
609643

610644
class Param(object):
611645
"""A ROS parameter.

src/roslibpy/ros1/actionlib.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
2-
Actionlib
3-
=========
2+
Actionlib (ROS 1)
3+
=================
44
55
Another way to interact with ROS is through the **actionlib** stack. Actions in
66
ROS allow to execute preemptable tasks, i.e. tasks that can be interrupted by the client.

tests/ros1/test_actionlib.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import time
44

55
from roslibpy import Ros
6-
from roslibpy.actionlib import ActionClient, Goal, GoalStatus, SimpleActionServer
6+
from roslibpy.ros1.actionlib import ActionClient, Goal, GoalStatus, SimpleActionServer
77

88

99
def test_action_success():

tests/ros2/test_actions.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from __future__ import print_function
2+
3+
import time
4+
5+
from roslibpy import Ros
6+
from roslibpy import ActionClient, ActionGoal, ActionGoalStatus
7+
8+
def test_fibonacci():
9+
ros = Ros("127.0.0.1", 9090)
10+
ros.run()
11+
12+
action = ActionClient(
13+
ros,
14+
'/fibonacci',
15+
'example_interfaces/action/Fibonacci'
16+
)
17+
18+
results = {}
19+
20+
def on_result(result):
21+
print(f"Result: {result}")
22+
results["result"] = result
23+
24+
def on_feedback(feedback):
25+
print(f"Feedback: {feedback}")
26+
27+
def on_error(error):
28+
print(f"Error: {error}")
29+
30+
goal = ActionGoal({"order": 4})
31+
goal_id = action.send_goal(goal, on_result, on_feedback, on_error)
32+
action.wait_goal(goal_id)
33+
time.sleep(0.2)
34+
35+
assert results["result"]["values"]["sequence"] == [0, 1, 1, 2, 3]
36+
assert results["result"]["status"] == ActionGoalStatus.SUCCEEDED
37+
38+
ros.close()
39+
40+
def test_cancel():
41+
ros = Ros("127.0.0.1", 9090)
42+
ros.run()
43+
44+
action = ActionClient(
45+
ros,
46+
'/fibonacci',
47+
'example_interfaces/action/Fibonacci'
48+
)
49+
50+
results = {}
51+
52+
def on_result(result):
53+
print(f"Result: {result}")
54+
results["result"] = result
55+
56+
def on_feedback(feedback):
57+
print(f"Feedback: {feedback}")
58+
59+
def on_error(error):
60+
print(f"Error: {error}")
61+
62+
goal = ActionGoal({"order": 10})
63+
goal_id = action.send_goal(goal, on_result, on_feedback, on_error)
64+
time.sleep(2)
65+
action.cancel_goal(goal_id)
66+
action.wait_goal(goal_id)
67+
time.sleep(0.2)
68+
69+
assert results["result"]["status"] == ActionGoalStatus.CANCELED
70+
71+
ros.close()
72+
73+
if __name__ == "__main__":
74+
test_cancel()

0 commit comments

Comments
 (0)