Creating ROS services
#!/usr/bin/env python
import random
def generate_ee_position(min_x, max_x, min_y, max_y, min_z, max_z):
"""
Generate a random position within the input area
"""
x_value = random.uniform(min_x, max_x)
y_value = random.uniform(min_y, max_y)
z_value = random.uniform(min_z, max_z)
return [x_value, y_value, z_value]
The C++ equivalent of this code would be the following:
#include <random>
#include <vector>
// You can define here as many functions as you want
float random_float(float min, float max)
{
// this function assumes max > min, you may want
// more robust error checking for a non-debug build
assert(max > min);
float random = ((float) rand()) / (float) RAND_MAX;
float range = max - min;
return (random * range) + min;
}
std::vector<float> generate_ee_position(float min_x, float max_x, float min_y, float max_y, float min_z, float max_z)
{
float x_value = random_float(min_x, max_x);
float y_value = random_float(min_y, max_y);
float z_value = random_float(min_z, max_z);
std::vector<float> output_vector{ x_value, y_value, z_value };
return output_vector;
}
The following instructions hold whether your code is implmented in C++ or Python!
1. Copy the server_template folder
Instead of creating a ROS package from scratch, we are going to start from an existing one. First create an empty folder in /home/projects/shadow/base/src
, which name will be the name of the ROS package containing your code and copy the content of this repository inside it. You can also initialize a repository if you want to keep it in git. In our case, we named our ROS package example_package
. This folder contains five folders and seven files, as follows:
+-- action
|
+-- include
| +-- server_template
| template_action_server.hpp
|
+-- msg
|
+-- scripts
| template_action_server.cpp
| template_action_server.py
| template_service_server.cpp
| template_service_server.py
|
+-- srv
CMakeLists.txt
package.xml
If your code is written in C++, all the headers (.hpp files) must be placed in /include/server_template
, while your .cpp files can be located in scripts
. If you are using python, just add your .py files in scripts
.
2. Change the package description
package.xml
file. In line 2 of CMakeLists.txt
, modify what’s inside project()
. The name inside project()
in the CMakeLists.txt
and the name inside <name>
tage of package.xml
must correspond to the name of your folder!. We encourage you to just replace server_template
by the name of your package in those two files.Warning
Make sure to change the name of the folder in include
as well!
cd /home/projects/shadow_robot/base catkin_make
If it does not finish successfully, it might be that the name of the folder and the name you changed in package.xml
and CMakeLists.txt
don’t match!
3. Create msg and srv files
<msg_type> input
---
int8 outcome
<msg_type> returned_object
<msg_type>
is left to your preference, i.e. it’s implementation-dependent. You can use all the built-in types or create your own msg file.GeneratePosition.srv
, in the srv
folder with the following contentfloat32[] input
---
int8 outcome
float32[] returned_object
CMakeLists.txt
file. Go to line 67 and add the name of your srv file after FILES
(if you have several, add one per line). In our case, we would getadd_service_files(
FILES
GeneratePosition.srv
)
cd /home/user/projects/shadow_robot/base catkin_make source devel/setup.bash
4. Filling the templates
template_service_server.cpp
file in scripts
. If you are using python, the template file is template_service_server.py
. Although the syntaxes are different, the steps are exactly the same (and described in the files). You can either copy/paste and rename these files or just rename them. In our case we are going to create the file generate_ee_position_server.py
(python version) and generate_ee_position_server.cpp
(C++ version).4.1 Import the generated srv files
Make sure to change the occurences of server_template
by your package name (e.g. example_package
). Then, change srvName
by the name of your srv file without the extension (e.g. GeneratePosition
).
4.2 Add your code
my_function
(feel free to change its name), or create a new function, class or whatever you prefer.input
field of your srv file inside my_function
, you can use input_values = request.input
and carry out any operation on the variable input_values
.<variable_type> input_values = request.input;
.4.3 Return the result
Make sure to fill and return the response of your server, keeping in mind that it must contain the fields outcome
and returned_object
.
4.4 Call your code when the script is executed
Change the name of the ROS node that will run your server (string inside rospy.init_node()
or ros::init()
). The last step is to make sure its content is valid. The first argument sets the name of the service, the second one (not in C++) should be the name of your srv file and the last one should be the name of the function returning the server response.
In our example, the generate_ee_position_server.py
looks like this:
#!/usr/bin/env python
import rospy
# Change server_template by the name of your ROS package and srvName by the name of your srv file (without the .srv)
from example_package.srv import GeneratePosition, GeneratePositionResponse
# You can add here any other import statement you might need for your code
import random
# You can define here as many functions as you want
def generate_ee_position(min_x, max_x, min_y, max_y, min_z, max_z):
"""
Generate a random position within the input area
"""
x_value = random.uniform(min_x, max_x)
y_value = random.uniform(min_y, max_y)
z_value = random.uniform(min_z, max_z)
return [x_value, y_value, z_value]
# Function that is going to be linked to the service server
def randomly_generate_end_effector_position(request):
"""
This function (feel free to change its name) must contain your code.
It MUST have a single input argument, that will be the request part of the srv, so DO NOT change it.
You can call other functions inside it without any problem
"""
# Change srvName by the name of your srv
response = GeneratePositionResponse()
# Get the input list
input_values = request.input
# Make sure the input list has 6 values, otherwise fill the response with a negative outcome (indexed as 1 here)
if len(input_values) != 6:
response.outcome = 1
# Return an empty list
response.returned_object = list()
else:
response.returned_object = generate_ee_position(input_values[0], input_values[1], input_values[2],
input_values[3], input_values[4], input_values[5])
# Success outcome
response.outcome = 0
# Return the response
return response
if __name__ == '__main__':
# Initialise the node with a specific name (please change it to match your service)
rospy.init_node('python_generate_ee_position_service_server')
# Set the name of the service, specify which kind of srv will trigger it and what function will be run.
# Change the name of the server with one that matches the content of your code, set the second argument to the name
# of the srv file, and the last one should be the name of the function that runs your code.
service = rospy.Service("ee_position_generation", GeneratePosition, randomly_generate_end_effector_position)
rospy.spin()
Similarly, here is the content of generate_ee_position_server.cpp
:
#include <ros/ros.h>
/**
Change server_template by the name of your ROS package and srvName by the name of your srv file (without the .srv)
*/
#include <example_package/GeneratePosition.h>
// You can add here any other include statement you might need for your code
#include <random>
#include <vector>
// You can define here as many functions as you want
float random_float(float min, float max)
{
// this function assumes max > min, you may want more robust error checking for a non-debug build
assert(max > min);
float random = ((float) rand()) / (float) RAND_MAX;
float range = max - min;
return (random * range) + min;
}
std::vector<float> generate_ee_position(float min_x, float max_x, float min_y, float max_y, float min_z, float max_z)
{
float x_value = random_float(min_x, max_x);
float y_value = random_float(min_y, max_y);
float z_value = random_float(min_z, max_z);
std::vector<float> output_vector{ x_value, y_value, z_value };
return output_vector;
}
// Function that is going to be linked to the service server
// This function (feel free to change its name) must contain your code.
// It MUST have two paramters, one for the request the srv, and the other one for the response.
// You can call other functions inside it without any problem
bool randomly_generate_end_effector_position(example_package::GeneratePosition::Request &request,
example_package::GeneratePosition::Response &response)
{
// Get the input list
std::vector<float> input_values = request.input;
// Make sure the input list has 6 values, otherwise fill the response with a negative outcome (indexed as 1 here)
if (input_values.size() != 6)
{
response.outcome = 1;
// Return an empty list
std::vector<float> empty_vector;
response.returned_object = empty_vector;
}
else
{
response.returned_object = generate_ee_position(input_values[0], input_values[1], input_values[2],
input_values[3], input_values[4], input_values[5]);
// Success outcome
response.outcome = 0;
}
// Return true to avoid having runtime errors on the client side
return true;
}
int main(int argc, char **argv)
{
// Initialise the node with a specific name (please change it to match your service)
ros::init(argc, argv, "cpp_generate_ee_position_service_server");
// Create a node handler
ros::NodeHandle node_handle;
/**
Set the name of the service and what function will be run and received
Change the name of the server with one that matches the content of your code, and the last one should be
the function that runs your code.
*/
ros::ServiceServer service = node_handle.advertiseService("ee_position_generation", randomly_generate_end_effector_position);
ros::spin();
return 0;
}
5. Make sure the server is executable
5.1 For Python
If you have created your server from template_service_server.py
, be sure to make your new file executable with chmod +x
:
chmod +x /home/user/projects/shadow_robot/base/src/example_package/scripts/generate_ee_position_server.py
Otherwise, ROS won’t be able to locate your node.
5.2 For C++
In order to make ROS aware of your newly created server, we need to slightly modify CMakeLists.txt
. In the Build section of this file (you can go to line 167), add these three lines for each cpp service file you have created:
add_executable(<node_name> scripts/<cpp_file_name>)
target_link_libraries(<node_name> ${catkin_LIBRARIES})
add_dependencies(<node_name> <package_name>)
In our case, we would have:
add_executable(cpp_generate_ee_position_service_server scripts/generate_ee_position_server.cpp)
target_link_libraries(cpp_generate_ee_position_service_server ${catkin_LIBRARIES})
add_dependencies(cpp_generate_ee_position_service_server example_package)
5.3 Common step
The last thing you need to do is to recompile your ROS package:
cd /home/projects/shadow_robot/base catkin_make source devel/setup.bash
And here you are! You have successfully wrapped your code in a ROS service fully compatible with GRIP!