Skip to content

Week 5 Action Client

Tom Howard edited this page Mar 8, 2022 · 7 revisions

Week 5 ROS Actions

Client Node

The Code

Copy all of the code below into your action_client.py file. Then, review the explainer below to understand how this all works.

#!/usr/bin/env python3

import rospy
import actionlib

from com2009_msgs.msg import CameraSweepAction, CameraSweepGoal, CameraSweepFeedback

node_name = "camera_sweep_action_client"
action_server_name = "/camera_sweep_action_server"

captured_images = 0
def feedback_callback(feedback_data: CameraSweepFeedback):
    global captured_images
    captured_images = feedback_data.{BLANK}
    print(f"FEEDBACK: Current yaw: {feedback_data.current_angle:.1f} degrees. "
        f"Image(s) captured so far: {captured_images}...")

rospy.init_node(node_name)

client = actionlib.SimpleActionClient(action_server_name, 
            CameraSweepAction)
client.wait_for_server()

goal = CameraSweepGoal()
goal.sweep_angle = 90
goal.image_count = 10

client.send_goal(goal, feedback_cb=feedback_callback)

client.wait_for_result()

print(f"RESULT: Action State = {client.get_state()}")
print(f"RESULT: {captured_images} images saved to {client.get_result()}")

FILL IN THE {BLANK}! Which attribute of the feedback_data object tells us how many images have been captured over the course of the Camera Sweep Action? (We discussed this in the "What is a ROS Action?" Section, but you could also run rosmsg info com2009_msgs/CameraSweepFeedback in a terminal to help you with this).

The Code Explained:

DFTS, of course!

As you know by now, in order to develop ROS nodes using Python we need to use the rospy library. If we want to work with ROS Actions, we also need to import actionlib. We already know the type of messages that we need to use to interact with the /camera_sweep_action_server, so we import that message type too (from the com2009_msgs package where that message type is defined). We need to import the full message definition: CameraSweepAction as well as the Goal message (which we use to actually make a call to the server). In addition to this, we'll also import the Feedback portion of the message too, which helps when building the feedback callback function:

import rospy
import actionlib

from com2009_msgs.msg import CameraSweepAction, CameraSweepGoal, CameraSweepFeedback

ROS Actions provide feedback, so we also imported the Feedback portion of the CameraSweepAction message into our node in the above as well. We then define a callback function for when we call the server, which will process that feedback data as it is published:

captured_images = 0
def feedback_callback(feedback_data: CameraSweepFeedback):
    global captured_images
    captured_images = feedback_data.{BLANK}
    print(f"FEEDBACK: Current yaw: {feedback_data.current_angle:.1f} degrees. "
        f"Image(s) captured so far: {captured_images}...")

Here we are using Python's "Type Annotations" feature:

def feedback_callback(feedback_data: CameraSweepFeedback):
    ...

Which informs the interpreter that the feedback_data that is received by the feedback_callback function will be of the CameraSweepFeedback type. All this really does is allow autocomplete functionality to work with in our editor (VS Code), whenever we want to pull an attribute from the feedback_data object. So, moving on...

As is standard practice when we construct ROS nodes, we must initialise them with a node name:

rospy.init_node(node_name)

Where the node_name variable was assigned earlier on in the code as:

node_name = "camera_sweep_action_client"

Then, we connect to the action server using the actionlib SimpleActionClient() method, provide this with the name of the server that we wish to connect to and the type of messages that are used on the server (in this case CameraSweepAction messages):

client = actionlib.SimpleActionClient(action_server_name, 
            CameraSweepAction)

Where the action_server_name variable was also assigned earlier on in the code too:

action_server_name = "/camera_sweep_action_server"

After that, the node simply waits until the action server is activated (if it isn't already):

client.wait_for_server()

Once the server is available, we construct a goal message and send this to the action server, whilst also pointing it to the callback function that we defined earlier, which will be used to process the feedback messages:

goal = CameraSweepGoal()
goal.sweep_angle = 90
goal.image_count = 10

client.send_goal(goal, feedback_cb=feedback_callback)

Then, we simply wait for the action to complete, by using the wait_for_result() method. Once it has completed, the server provides us with a result, so we simply print that (as well as the current state of the action) to the terminal:

client.wait_for_result()

print(f"RESULT: Action State = {client.get_state()}")
print(f"RESULT: {captured_images} images saved to {client.get_result()}")
Clone this wiki locally