class node python,ROS/Tutorials

本文介绍如何在ROS环境中将C++类与Python进行绑定,利用Boost.Python库实现C++类与ROS消息之间的序列化及反序列化。通过具体实例展示了创建包装器类、设置CMakeLists.txt文件等步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

6da015a8d37b40830bd0ceda913832f2.png Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.

Using a C++ class in Python

Description: This tutorial illustrates a way to use a C++ class with ROS messages in Python.

Keywords: C++, Python, bindings

Tutorial Level: ADVANCED

Next Tutorial: Packaging your ROS project as a snap

catkin

rosbuild

This tutorial illustrates a way to use a C++ class with ROS messages in Python. The Boost Python library is used. The difficulty is to translate Python objects of ROS messages written in pure Python into equivalent C++ instances. This translation will be done through serialization/deserialization. The source files can be found at https://github.com/galou/python_bindings_tutorial.

Another solution is to use classical ROS services, the server written in C++ will be a wrapper around the C++ class and the client, C++ or Python, will call the service. The solution proposed here does not create a ROS node, provided the class to be wrapped does not make use of ros::NodeHandle.

Another solution is to write a wrapper for all needed ROS messages and their dependencies. Some apparently deprecated package proposed some solutions for the automation of this task: genpybindings and boost_ python_ros.

Class without NodeHandle

Because roscpp is not initialized when calling rospy.init_node. ros::NodeHandle instances cannot be used in the C++ class without generating an error. If the C++ does not make use of ros::NodeHandle, this is no issue though.

Creating the package and writing the C++ class

Create a package and create the C++ class for which we will want to make a Python binding. This class uses ROS messages as arguments and return type.

roscreate-pkg # ... to be completed

cd python_bindings_tutorial/include/python_bindings_tutorial

touch add_two_ints.h

rosed python_bindings_tutorial add_two_ints.h

catkin_create_pkg python_bindings_tutorial rospy roscpp std_msgs

cd python_bindings_tutorial/include/python_bindings_tutorial

touch add_two_ints.h

rosed python_bindings_tutorial add_two_ints.h

The content of include/python_bindings_tutorial/add_two_ints.h will be:

#ifndef PYTHON_BINDINGS_TUTORIAL_ADD_TWO_INTS_H

#define PYTHON_BINDINGS_TUTORIAL_ADD_TWO_INTS_H

#include

namespace python_bindings_tutorial {

class AddTwoInts

{

public:

std_msgs::Int64 add(const std_msgs::Int64& a, const std_msgs::Int64& b);

};

} // namespace python_bindings_tutorial

#endif// PYTHON_BINDINGS_TUTORIAL_ADD_TWO_INTS_H

Write the class implementation into .

roscd python_bindings_tutorial/src

touch add_two_ints.cpp

rosed python_bindings_tutorial add_two_ints.cpp

The content of src/add_two_ints.cpp will be:

#include

using namespace python_bindings_tutorial;

std_msgs::Int64 AddTwoInts::add(const std_msgs::Int64& a, const std_msgs::Int64& b)

{

std_msgs::Int64 sum;

sum.data = a.data + b.data;

return sum;

Binding, C++ part

The binding occurs through two wrapper classes, one in C++ and one in Python. The C++ wrapper translates input from serialized content to C++ message instances and output from C++ message instances into serialized content.

roscd python_bindings_tutorial/src

touch add_two_ints_wrapper.cpp

rosed python_bindings_tutorial add_two_ints_wrapper.cpp

The content of src/add_two_ints_wrapper.cpp will be:

#include

#include

#include

#include

#include

/* Read a ROS message from a serialized string.

*/

template

M from_python(const std::string str_msg)

size_t serial_size = str_msg.size();

boost::shared_array buffer(new uint8_t[serial_size]);

for (size_t i = 0; i < serial_size; ++i)

buffer[i] = str_msg[i];

ros::serialization::IStream stream(buffer.get(), serial_size);

M msg;

ros::serialization::Serializer::read(stream, msg);

return msg;

/* Write a ROS message into a serialized string.

*/

template

std::string to_python(const M& msg)

size_t serial_size = ros::serialization::serializationLength(msg);

boost::shared_array buffer(new uint8_t[serial_size]);

ros::serialization::OStream stream(buffer.get(), serial_size);

ros::serialization::serialize(stream, msg);

std::string str_msg;

str_msg.reserve(serial_size);

for (size_t i = 0; i < serial_size; ++i)

str_msg.push_back(buffer[i]);

return str_msg;

class AddTwoIntsWrapper : public python_bindings_tutorial::AddTwoInts

public:

AddTwoIntsWrapper() : AddTwoInts() {}

std::string add(const std::string& str_a, const std::string& str_b)

std_msgs::Int64 a = from_python<:int64>(str_a);

std_msgs::Int64 b = from_python<:int64>(str_b);

std_msgs::Int64 sum = AddTwoInts::add(a, b);

return to_python(sum);

};

BOOST_PYTHON_MODULE(_add_two_ints_wrapper_cpp)

boost::python::class_("AddTwoIntsWrapper", boost::python::init<>())

.def("add", &AddTwoIntsWrapper::add)

The line Error: No code_block found creates a Python module in the form of a dynamic library. The name of the module will have to be the name of the exported library in CMakeLists.txt.

Binding, Python part

The Python wrapper translates input from Python message instances into serialized content and output from serialized content to Python message instances. The translation from Python serialized content (str) into C++ serialized content (std::string) is built in the Boost Python library.

roscd python_bindings_tutorial/src

mkdir python_bindings_tutorial

roscd python_bindings_tutorial/src/python_bindings_tutorial

touch _add_two_ints_wrapper_py.py

rosed python_bindings_tutorial _add_two_ints_wrapper_py.py

The content of src/python_bindings_tutorial/_add_two_ints_wrapper_py.py will be

from StringIO import StringIO

import rospy

from std_msgs.msg import Int64

from python_bindings_tutorial._add_two_ints_wrapper_cpp import AddTwoIntsWrapper

class AddTwoInts(object):

def __init__(self):

self._add_two_ints = AddTwoIntsWrapper()

def _to_cpp(self, msg):

"""Return a serialized string from a ROS message

Parameters

----------

- msg: a ROS message instance.

"""

buf = StringIO()

msg.serialize(buf)

return buf.getvalue()

def _from_cpp(self, str_msg, cls):

"""Return a ROS message from a serialized string

Parameters

----------

- str_msg: str, serialized message

- cls: ROS message class, e.g. sensor_msgs.msg.LaserScan.

"""

msg = cls()

return msg.deserialize(str_msg)

def add(self, a, b):

"""Add two std_mgs/Int64 messages

Return a std_msgs/Int64 instance.

Parameters

----------

- a: a std_msgs/Int64 instance.

- b: a std_msgs/Int64 instance.

"""

if not isinstance(a, Int64):

rospy.ROSException('Argument 1 is not a std_msgs/Int64')

if not isinstance(b, Int64):

rospy.ROSException('Argument 2 is not a std_msgs/Int64')

str_a = self._to_cpp(a)

str_b = self._to_cpp(b)

str_sum = self._add_two_ints.add(str_a, str_b)

return self._from_cpp(str_sum, Int64)

In order to be able to import the class as python_bindings_tutorial.AddTwoInts, we import the symbols in __init__.py. First, we create the file:

roscd python_bindings_tutorial/src/python_bindings_tutorial

touch __init__.py

rosed python_bindings_tutorial __init__.py

The content of src/python_bindings_tutorial/__init__.py will be:

from python_bindings_tutorial._add_two_ints_wrapper_py import AddTwoInts

Glueing everything together

Edit the CMakeLists.txt (rosed python_bindings_tutorial CmakeLists.txt) like this:

Adapt CMakeLists.txt from the catkin version of this page.

cmake_minimum_required(VERSION 2.8.3)

project(python_bindings_tutorial)

find_package(catkin REQUIRED COMPONENTS

roscpp

roscpp_serialization

std_msgs

)

## Both Boost.python and Python libs are required.

find_package(Boost REQUIRED COMPONENTS python)

find_package(PythonLibs 2.7 REQUIRED)

## Uncomment this if the package has a setup.py. This macro ensures

## modules and global scripts declared therein get installed

## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html

catkin_python_setup()

###################################

## catkin specific configuration ##

###################################

catkin_package(

INCLUDE_DIRS include

LIBRARIES add_two_ints _add_two_ints_wrapper_cpp

CATKIN_DEPENDS roscpp

# DEPENDS system_lib

)

###########

## Build ##

###########

# include Boost and Python.

include_directories(

include

${catkin_INCLUDE_DIRS}

${Boost_INCLUDE_DIRS}

${PYTHON_INCLUDE_DIRS}

)

## Declare a cpp library

add_library(add_two_ints src/add_two_ints.cpp)

add_library(_add_two_ints_wrapper_cpp src/add_two_ints_wrapper.cpp)

## Specify libraries to link a library or executable target against

target_link_libraries(add_two_ints ${catkin_LIBRARIES})

target_link_libraries(_add_two_ints_wrapper_cpp add_two_ints ${catkin_LIBRARIES} ${Boost_LIBRARIES})

# Don't prepend wrapper library name with lib and add to Python libs.

set_target_properties(_add_two_ints_wrapper_cpp PROPERTIES

PREFIX ""

LIBRARY_OUTPUT_DIRECTORY ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION}

)

The c++ wrapper library should be have the same name as the Python module. If the target name needs to be different for a reason, the library name can be specified with set_target_properties(_add_two_ints_wrapper_cpp PROPERTIES OUTPUT_NAME correct_library_name).

The linecatkin_python_setup()

is used to export the Python module and is associated with the file setup.py

roscd python_bindings_tutorial

touch setup.py

The content of setup.py will be:

# ! DO NOT MANUALLY INVOKE THIS setup.py, USE CATKIN INSTEAD

from distutils.core import setup

from catkin_pkg.python_setup import generate_distutils_setup

# fetch values from package.xml

setup_args = generate_distutils_setup(

packages=['python_bindings_tutorial'],

package_dir={'': 'src'})

setup(**setup_args)

We then build the package with rosmake.

We then build the package with catkin_make.

Testing the binding

You can now use the binding in Python scripts or in a Python shell

from std_msgs.msg import Int64

from python_bindings_tutorial import AddTwoInts

a = Int64(4)

b = Int64(2)

addtwoints = AddTwoInts()

sum = addtwoints.add(a, b)

sum

Class with NodeHandle

As stated, a Python call to rospy.init_node does not initialize roscpp. If roscpp is not initialized, instanciating ros::NodeHandle will lead to a fatal error. A solution for this is provided by the moveit_ros_planning_interface. In any Python script that uses the wrapped class, two lines need to be added before instanciating AddTwoInts:

from moveit_ros_planning_interface._moveit_roscpp_initializer import roscpp_init

roscpp_init('node_name', [])

This will create a ROS node. The advantage of this method over a classical ROS service server/client implementation is thus not as clear as in the case without the need of ros::NodeHandle.

Class with container of ROS messages

If the class uses containers of ROS messages, an extra conversion step must be added. This step is not specific to ROS but is part of the Boost Python library.

`std::vector` as return type

In the file where the C++ wrapper class is defined, add these lines:

// Extracted from https://gist.github.com/avli/b0bf77449b090b768663.

template

struct vector_to_python

{

static PyObject* convert(const std::vector& vec)

{

boost::python::list* l = new boost::python::list();

for(std::size_t i = 0; i < vec.size(); i++)

(*l).append(vec[i]);

return l->ptr();

};

class Wrapper : public WrappedClass

/*

...

*/

std::vector<:string> wrapper_fun(const std::string str_msg)

/* ... */

};

BOOST_PYTHON_MODULE(module_wrapper_cpp)

boost::python::class_("Wrapper", bp::init* ... */>())

.def("fun", &Wrapper::wrapper_fun);

boost::python::to_python_converter<:vector std::allocator> >, vector_to_python<:string> >();

`std::vector` as argument type

Cf. Boost.Python documentation.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值