Skip to content

Commit d6f14c9

Browse files
committed
Allow action servers without execute callback
1 parent 06d78fb commit d6f14c9

File tree

2 files changed

+67
-4
lines changed

2 files changed

+67
-4
lines changed

rclpy/rclpy/action/server.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,21 @@ def publish_feedback(self, feedback):
146146
# Publish
147147
self._action_server._handle.publish_feedback(feedback_message)
148148

149-
def succeed(self):
149+
def executing(self):
150+
self._update_state(_rclpy.GoalEvent.EXECUTE)
151+
152+
def succeed(self, response=None):
150153
self._update_state(_rclpy.GoalEvent.SUCCEED)
151154

155+
# Set result
156+
result_response = self._action_server._action_type.Impl.GetResultService.Response()
157+
result_response.status = self.status
158+
if response is not None:
159+
result_response.result = response
160+
else:
161+
result_response.result = self._action_server._action_type.Result()
162+
self._action_server._result_futures[bytes(self.goal_id.uuid)].set_result(result_response)
163+
152164
def abort(self):
153165
self._update_state(_rclpy.GoalEvent.ABORT)
154166

@@ -186,7 +198,7 @@ def __init__(
186198
node,
187199
action_type,
188200
action_name,
189-
execute_callback,
201+
execute_callback=None,
190202
*,
191203
callback_group=None,
192204
goal_callback=default_goal_callback,
@@ -233,7 +245,10 @@ def __init__(
233245
self.register_handle_accepted_callback(handle_accepted_callback)
234246
self.register_goal_callback(goal_callback)
235247
self.register_cancel_callback(cancel_callback)
236-
self.register_execute_callback(execute_callback)
248+
if execute_callback:
249+
self.register_execute_callback(execute_callback)
250+
elif handle_accepted_callback is default_handle_accepted_callback:
251+
self._logger.warning("Not handling nor executing the goal, this server will do nothing")
237252

238253
# Import the typesupport for the action module if not already done
239254
check_for_type_support(action_type)

rclpy/test/test_action_server.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import rclpy
2323
from rclpy.action import ActionServer, CancelResponse, GoalResponse
24+
from rclpy.action.server import ServerGoalHandle
2425
from rclpy.callback_groups import ReentrantCallbackGroup
2526
from rclpy.executors import MultiThreadedExecutor, SingleThreadedExecutor
2627

@@ -419,7 +420,7 @@ def execute_callback(gh):
419420
# Execute the goal
420421
server_goal_handle.execute()
421422

422-
# Get the result and exepect it to have canceled status
423+
# Get the result and expect it to have canceled status
423424
get_result_future = self.mock_action_client.get_result(goal_uuid)
424425
rclpy.spin_until_future_complete(self.node, get_result_future, self.executor)
425426
result = get_result_future.result()
@@ -684,6 +685,53 @@ def execute_with_feedback(goal_handle):
684685
finally:
685686
action_server.destroy()
686687

688+
def test_without_execute_cb(self):
689+
# Just like test_execute_succeed, but without an execute callback
690+
# Goal handle is stored and succeeded from outside the execute callback
691+
stored_goal_handle = None
692+
693+
def handle_accepted_callback(goal_handle: ServerGoalHandle):
694+
goal_handle.executing()
695+
nonlocal stored_goal_handle
696+
stored_goal_handle = goal_handle
697+
698+
executor = MultiThreadedExecutor(context=self.context)
699+
700+
action_server = ActionServer(
701+
self.node,
702+
Fibonacci,
703+
'fibonacci',
704+
handle_accepted_callback=handle_accepted_callback,
705+
)
706+
707+
goal_uuid = UUID(uuid=list(uuid.uuid4().bytes))
708+
goal_msg = Fibonacci.Impl.SendGoalService.Request()
709+
goal_msg.goal_id = goal_uuid
710+
goal_future = self.mock_action_client.send_goal(goal_msg)
711+
rclpy.spin_until_future_complete(self.node, goal_future, executor)
712+
goal_handle = goal_future.result()
713+
self.assertTrue(goal_handle.accepted)
714+
715+
get_result_future = self.mock_action_client.get_result(goal_uuid)
716+
self.assertFalse(get_result_future.done())
717+
718+
result = Fibonacci.Result()
719+
result.sequence.extend([1, 1, 2, 3, 5])
720+
stored_goal_handle.succeed(result)
721+
722+
# Handle all callbacks
723+
for _ in range(5):
724+
rclpy.spin_once(self.node, executor=self.executor, timeout_sec=0.01)
725+
if get_result_future.done():
726+
break
727+
self.assertTrue(get_result_future.done())
728+
729+
result_response = get_result_future.result()
730+
731+
self.assertEqual(result_response.status, GoalStatus.STATUS_SUCCEEDED)
732+
self.assertEqual(result_response.result.sequence.tolist(), [1, 1, 2, 3, 5])
733+
action_server.destroy()
734+
687735

688736
if __name__ == '__main__':
689737
unittest.main()

0 commit comments

Comments
 (0)