The code used in this tutorial is available!
Code can be found at industrial_training repository in doc folder. Use kinetic-devel branch.
Services¶
In this exercise, we will create a custom service by defining a .srv file. Then we will write server and client nodes to utilize this service.
Motivation¶
The first type of ROS communication that we explored was a one-way interaction called messages which are sent over channels called topics. Now we are going to explore a different communication type, which is a two-way interaction via a request from one node to another and a response from that node to the first. In this module we will create a service server (waits for request and comes up with response) and client (makes request for info then waits for response).
Reference Example¶
Further Information and Resources¶
Scan-N-Plan Application: Problem Statement¶
We now have a base ROS node which is subscribing to some information and we want to build on this node. In addition we want this node to serve as a sub-function to another "main" node. The original vision node will now be responsible for subscribing to the AR information and responding to requests from the main workcell node.
Your goal is to create a more intricate system of nodes:
- Update the vision node to include a service server
- Create a new node which will eventually run the Scan-N-Plan App
- First, we'll create the new node (myworkcell_core) as a service client. Later, we will expand from there
Scan-N-Plan Application: Guidance¶
Create Service Definition¶
Similar to the message file located in the fake_ar_publisher package, we need to create a service file. The following is a generic structure of a service file:
#request --- #response
Create a folder called
srvinside yourmyworkcell_corepackage (at same level as the package'ssrcfolder)cd ~/catkin_ws/src/myworkcell_core mkdir srv
Create a file (gedit or QT) called
LocalizePart.srvinside thesrvfolder.Inside the file, define the service as outlined above with a request of type
stringnamedbase_frameand a response of typegeometry_msgs/Posenamedpose:#request string base_frame --- #response geometry_msgs/Pose pose
Edit the package's
CMakeLists.txtandpackage.xmlto add dependencies on key packages:message_generationis required to build C++ code from the .srv file created in the previous stepmessage_runtimeprovides runtime dependencies for new messagesgeometry_msgsprovides thePosemessage type used in our service definition
Edit the package's
CMakeLists.txtfile to add the new build-time dependencies to the existingfind_packagerule:find_package(catkin REQUIRED COMPONENTS roscpp fake_ar_publisher geometry_msgs message_generation )
Also in
CMakeLists.txt, add the new run-time dependencies to the existingcatkin_packagerule:catkin_package( # INCLUDE_DIRS include # LIBRARIES myworkcell_node CATKIN_DEPENDS roscpp fake_ar_publisher message_runtime geometry_msgs # DEPENDS system_lib )
Edit the
package.xmlfile to add the appropriate build/run dependencies:<build_depend>message_generation</build_depend> <run_depend>message_runtime</run_depend> <build_depend>geometry_msgs</build_depend> <run_depend>geometry_msgs</run_depend>
Edit the package's
CMakeLists.txtto add rules to generate the new service files:Uncomment/edit the following
CMakeLists.txtrule to reference theLocalizePartservice we defined earlier:## Generate services in the 'srv' folder add_service_files( FILES LocalizePart.srv )
Uncomment/edit the following
CMakeLists.txtrule to enable generation of messages and services:## Generate added messages and services with any dependencies listed here generate_messages( DEPENDENCIES geometry_msgs )
NOW! you have a service defined in you package and you can attempt to Build the code to generate the service:
catkin build
Note: (or use Qt!)
Service Server¶
Edit
vision_node.cpp; remember that the ros wiki is a resource.Add the header for the service we just created
#include <myworkcell_core/LocalizePart.h>
Add a member variable (type:
ServiceServer, name:server_), near the otherLocalizerclass member variables:ros::ServiceServer server_;
In the
Localizerclass constructor, advertise your service to the ROS master:server_ = nh.advertiseService("localize_part", &Localizer::localizePart, this);
The
advertiseServicecommand above referenced a service callback namedlocalizePart. Create an empty boolean function with this name in theLocalizerclass. Remember that your request and response types were defined in theLocalizePart.srvfile. The arguments to the boolean function are the request and response type, with the general structure ofPackage::ServiceName::RequestorPackage::ServiceName::Response.bool localizePart(myworkcell_core::LocalizePart::Request& req, myworkcell_core::LocalizePart::Response& res) { }
Now add code to the
localizePartcallback function to fill in the Service Response. Eventually, this callback will transform the pose received from thefake_ar_publisher(invisionCallback) into the frame specifed in the Service Request. For now, we will skip the frame-transform, and just pass through the data received fromfake_ar_publisher. Copy the pose measurement received fromfake_ar_publisher(saved tolast_msg_) directly to the Service Response.bool localizePart(myworkcell_core::LocalizePart::Request& req, myworkcell_core::LocalizePart::Response& res) { // Read last message fake_ar_publisher::ARMarkerConstPtr p = last_msg_; if (!p) return false; res.pose = p->pose.pose; return true; }
You should comment out the
ROS_INFO_STREAMcall in yourvisionCallbackfunction, to avoid cluttering the screen with useless info.Build the updated
vision_node, to make sure there are no compile errors.
Service Client¶
Create a new node (inside the same
myworkcell_corepackage), namedmyworkcell_node.cpp. This will eventually be our main "application node", that controls the sequence of actions in our Scan & Plan task. The first action we'll implement is to request the position of the AR target from the Vision Node'sLocalizePartservice we created above.Be sure to include the standard ros header as well as the header for the
LocalizePartservice:#include <ros/ros.h> #include <myworkcell_core/LocalizePart.h>
Create a standard C++ main function, with typical ROS node initialization:
int main(int argc, char **argv) { ros::init(argc, argv, "myworkcell_node"); ros::NodeHandle nh; ROS_INFO("ScanNPlan node has been initialized"); ros::spin(); }
We will be using a cpp class "ScanNPlan" to contain most functionality of the myworkcell_node. Create a skeleton structure of this class, with an empty constructor and a private area for some internal/private variables.
class ScanNPlan { public: ScanNPlan(ros::NodeHandle& nh) { } private: // Planning components };
Within your new ScanNPlan class, define a ROS ServiceClient as a private member variable of the class. Initialize the ServiceClient in the ScanNPlan constructor, using the same service name as defined earlier ("localize_part"). Create a void function within the ScanNPlan class named
start, with no arguments. This will contain most of our application workflow. For now, this function will call theLocalizePartservice and print the response.class ScanNPlan { public: ScanNPlan(ros::NodeHandle& nh) { vision_client_ = nh.serviceClient<myworkcell_core::LocalizePart>("localize_part"); } void start() { ROS_INFO("Attempting to localize part"); // Localize the part myworkcell_core::LocalizePart srv; if (!vision_client_.call(srv)) { ROS_ERROR("Could not localize part"); return; } ROS_INFO_STREAM("part localized: " << srv.response); } private: // Planning components ros::ServiceClient vision_client_; };
Now back in
myworkcell_node'smainfunction, instantiate an object of theScanNPlanclass and call the object'sstartfunction.ScanNPlan app(nh); ros::Duration(.5).sleep(); // wait for the class to initialize app.start();
Edit the package's
CMakeLists.txtto build the new node (executable), with its associated dependencies. Add the following rules to the appropriate sections, directly under the matching rules forvision_node:add_executable(myworkcell_node src/myworkcell_node.cpp) add_dependencies(myworkcell_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) target_link_libraries(myworkcell_node ${catkin_LIBRARIES})
Build the nodes to check for any compile-time errors:
catkin build
Note: (or use Qt!)
Use New Service¶
Enter each of these commands in their own terminal:
roscore rosrun fake_ar_publisher fake_ar_publisher_node rosrun myworkcell_core vision_node rosrun myworkcell_core myworkcell_node
Open Source Feedback
See something that needs improvement? Please open a pull request on this GitHub page