Ros2 源码解析与实践 - Node

Node定义

ROS1与ROS2的定义区别

  • ROS1和ROS2在功能上没有区别(提供publish,subscribe,client,service等角色功能)
  • 生命周期
    • ROS1中每个node的初始化都在一个main函数中,都对应一个进程。main函数中,通过初始化一个node得到一个node handle传入自定义的node behavior构造函数来定义node行为
    • ROS2中每个node都继承自一个基类rclcpp::Node,采用子类继承的方式来暴露接口,这样每个node的生命周期都得到有效的控制。(这个lifecycle定义在更底层rcl层)
  • ROS2 Node提供的创建服务接口,node实例继承自基类,创建函数返回的都是SharedPtr:
    • rclcpp::Node->create_callback_group
    • rclcpp::Node->create_publisher
    • rclcpp::Node->create_subscription
    • rclcpp::Node->create_client
    • rclcpp::Node->create_service
    • rclcpp::Node->create_subnode
    • rclcpp::Node->create_wall_timer

node.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
//std::enable_shared_from_this<T> 能让一个(类型是T,且已经被shared_ptr管理)安全地生成额外的std::shared_ptr。(定义于<memory>, 所以只要继承rclcpp::Node就必须要添加memory)
class Node : public std::enable_shared_from_this<Node>
{
public:
RCLCPP_SMART_PTR_DEFINITIONS(Node) //"rclcpp/macros.hpp" 生成指针SharedPtr

/// Create a new node with the specified name.
/**
* \param[in] node_name Name of the node.
* \param[in] options Additional options to control creation of the node.
*/
RCLCPP_PUBLIC //"rclcpp/visibility_control.hpp"
explicit Node( //禁止隐式转换的构造函数。
const std::string & node_name,
const NodeOptions & options = NodeOptions());

/// Create a new node with the specified name.
/**
* \param[in] node_name Name of the node.
* \param[in] namespace_ Namespace of the node.
* \param[in] options Additional options to control creation of the node.
*/
RCLCPP_PUBLIC
explicit Node(
const std::string & node_name,
const std::string & namespace_,
const NodeOptions & options = NodeOptions());

RCLCPP_PUBLIC
virtual ~Node();

/// Get the name of the node.
/** \return The name of the node. */
RCLCPP_PUBLIC
const char * //Node::name一旦定义就不能改变
get_name() const; //声明const不可修改成员变量

/// Get the namespace of the node.
/**
* This namespace is the "node's" namespace, and therefore is not affected
* by any sub-namespace's that may affect entities created with this instance.
* Use get_effective_namespace() to get the full namespace used by entities.
*
* \sa get_sub_namespace()
* \sa get_effective_namespace()
* \return The namespace of the node.
*/
RCLCPP_PUBLIC
const char *
get_namespace() const;

/// Get the fully-qualified name of the node.
/**
* The fully-qualified name includes the local namespace and name of the node.
*/
RCLCPP_PUBLIC
const char *
get_fully_qualified_name() const;

/// Get the logger of the node.
/** \return The logger of the node. */
RCLCPP_PUBLIC
rclcpp::Logger
get_logger() const;

/// Create and return a callback group.
RCLCPP_PUBLIC
rclcpp::callback_group::CallbackGroup::SharedPtr
create_callback_group(rclcpp::callback_group::CallbackGroupType group_type);

/// Return the list of callback groups in the node.
RCLCPP_PUBLIC
const std::vector<rclcpp::callback_group::CallbackGroup::WeakPtr> &
get_callback_groups() const;

/// Create and return a Publisher.
/**
* The rclcpp::QoS has several convenient constructors, including a
* conversion constructor for size_t, which mimics older API's that
* allows just a string and size_t to create a publisher.
*
* For example, all of these cases will work:
*
* cpp
* pub = node->create_publisher<MsgT>("chatter", 10); // implicitly KeepLast
* pub = node->create_publisher<MsgT>("chatter", QoS(10)); // implicitly KeepLast
* pub = node->create_publisher<MsgT>("chatter", QoS(KeepLast(10)));
* pub = node->create_publisher<MsgT>("chatter", QoS(KeepAll()));
* pub = node->create_publisher<MsgT>("chatter", QoS(1).best_effort().volatile());
* {
* rclcpp::QoS custom_qos(KeepLast(10), rmw_qos_profile_sensor_data);
* pub = node->create_publisher<MsgT>("chatter", custom_qos);
* }
*
*
* The publisher options may optionally be passed as the third argument for
* any of the above cases.
*
* \param[in] topic_name The topic for this publisher to publish on.
* \param[in] qos The Quality of Service settings for the publisher.
* \param[in] options Additional options for the created Publisher.
* \return Shared pointer to the created publisher.
*/
template<
typename MessageT,
typename AllocatorT = std::allocator<void>,
typename PublisherT = rclcpp::Publisher<MessageT, AllocatorT>>
std::shared_ptr<PublisherT>
create_publisher(
const std::string & topic_name,
const rclcpp::QoS & qos,
const PublisherOptionsWithAllocator<AllocatorT> & options =
PublisherOptionsWithAllocator<AllocatorT>()
);

//publisherT指针,定义topic_name, qos和相关options

/// Create and return a Subscription.
/**
* \param[in] topic_name The topic to subscribe on.
* \param[in] callback The user-defined callback function to receive a message
* \param[in] qos_history_depth The depth of the subscription's incoming message queue.
* \param[in] options Additional options for the creation of the Subscription.
* \param[in] msg_mem_strat The message memory strategy to use for allocating messages.
* \return Shared pointer to the created subscription.
*/
template<
typename MessageT,
typename CallbackT,
typename AllocatorT = std::allocator<void>,
typename CallbackMessageT =
typename rclcpp::subscription_traits::has_message_type<CallbackT>::type,
typename SubscriptionT = rclcpp::Subscription<CallbackMessageT, AllocatorT>,
typename MessageMemoryStrategyT = rclcpp::message_memory_strategy::MessageMemoryStrategy<
CallbackMessageT,
AllocatorT
>
>
std::shared_ptr<SubscriptionT>
create_subscription(
const std::string & topic_name,
const rclcpp::QoS & qos,
CallbackT && callback,
const SubscriptionOptionsWithAllocator<AllocatorT> & options =
SubscriptionOptionsWithAllocator<AllocatorT>(),
typename MessageMemoryStrategyT::SharedPtr msg_mem_strat = (
MessageMemoryStrategyT::create_default()
)
);

//sub消息指针,qos,相关triger callback, options

/// Create a timer.
/**
* \param[in] period Time interval between triggers of the callback.
* \param[in] callback User-defined callback function.
* \param[in] group Callback group to execute this timer's callback in.
*/
template<typename DurationRepT = int64_t, typename DurationT = std::milli, typename CallbackT>
typename rclcpp::WallTimer<CallbackT>::SharedPtr
create_wall_timer(
std::chrono::duration<DurationRepT, DurationT> period,
CallbackT callback,
rclcpp::callback_group::CallbackGroup::SharedPtr group = nullptr);

//timer定时器,定时区间period,trigger callback

//service和client
/* Create and return a Client. */
template<typename ServiceT>
typename rclcpp::Client<ServiceT>::SharedPtr
create_client(
const std::string & service_name,
const rmw_qos_profile_t & qos_profile = rmw_qos_profile_services_default,
rclcpp::callback_group::CallbackGroup::SharedPtr group = nullptr);


/* Create and return a Service. */
template<typename ServiceT, typename CallbackT>
typename rclcpp::Service<ServiceT>::SharedPtr
create_service(
const std::string & service_name,
CallbackT && callback,
const rmw_qos_profile_t & qos_profile = rmw_qos_profile_services_default,
rclcpp::callback_group::CallbackGroup::SharedPtr group = nullptr);


//参数设置部分
RCLCPP_PUBLIC
const rclcpp::ParameterValue &
declare_parameter(
const std::string & name,
const rclcpp::ParameterValue & default_value = rclcpp::ParameterValue(),
const rcl_interfaces::msg::ParameterDescriptor & parameter_descriptor =
rcl_interfaces::msg::ParameterDescriptor(),
bool ignore_override = false);

//泛型编程重载函数1
template<typename ParameterT>
auto
declare_parameter(
const std::string & name,
const ParameterT & default_value,
const rcl_interfaces::msg::ParameterDescriptor & parameter_descriptor =
rcl_interfaces::msg::ParameterDescriptor(),
bool ignore_override = false);

//重载2
template<typename ParameterT>
std::vector<ParameterT>
declare_parameters(
const std::string & namespace_,
const std::map<std::string, ParameterT> & parameters,
bool ignore_overrides = false);

//重载3
template<typename ParameterT>
std::vector<ParameterT>
declare_parameters(
const std::string & namespace_,
const std::map<
std::string,
std::pair<ParameterT, rcl_interfaces::msg::ParameterDescriptor>
> & parameters,
bool ignore_overrides = false);

//删除parameter
RCLCPP_PUBLIC
void
undeclare_parameter(const std::string & name);

RCLCPP_PUBLIC
bool
has_parameter(const std::string & name) const;

RCLCPP_PUBLIC
rcl_interfaces::msg::SetParametersResult
set_parameter(const rclcpp::Parameter & parameter);

RCLCPP_PUBLIC
std::vector<rcl_interfaces::msg::SetParametersResult>
set_parameters(const std::vector<rclcpp::Parameter> & parameters);

RCLCPP_PUBLIC
rcl_interfaces::msg::SetParametersResult
set_parameters_atomically(const std::vector<rclcpp::Parameter> & parameters);

... //相关parameters的重载函数非常多。不列举了


//成员变量
protected:
/// Construct a sub-node, which will extend the namespace of all entities created with it.
/**
* \sa create_sub_node()
*
* \param[in] other The node from which a new sub-node is created.
* \param[in] sub_namespace The sub-namespace of the sub-node.
*/
RCLCPP_PUBLIC
Node(
const Node & other,
const std::string & sub_namespace);

//私有成员变量
private:
RCLCPP_DISABLE_COPY(Node) //删除拷贝构造函数

RCLCPP_PUBLIC
bool
group_in_node(callback_group::CallbackGroup::SharedPtr group);

rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_;
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph_;
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_;
rclcpp::node_interfaces::NodeTimersInterface::SharedPtr node_timers_;
rclcpp::node_interfaces::NodeTopicsInterface::SharedPtr node_topics_;
rclcpp::node_interfaces::NodeServicesInterface::SharedPtr node_services_;
rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_;
rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_;
rclcpp::node_interfaces::NodeTimeSourceInterface::SharedPtr node_time_source_;
rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_;

const rclcpp::NodeOptions node_options_;
const std::string sub_namespace_;
const std::string effective_namespace_;
};

相关C++11的语法:

继承自std::enable_shared_from_this

https://blog.csdn.net/fm_VAE/article/details/79660768
在智能指针的使用过程中我们会遇到这样一种情况,我们在类的成员函数调用某一个函数,而该函数需要传递一个当前对象的智能指针作为参数时,我们需要能够在成员函数中获得自己的智能指针。传递这个智能指针可以增加指针的引用次数,放置在函数执行的过程中,对象被释放而引发引用错误。
但是我们不能在类中使用this构建一个这样的shared_ptr, 不是成员函数的shared_ptr是在栈中生成的,一旦超出作用域,shared_ptr被析构,会导致这个对象本身的this被析构,从而导致致命错误。

为了解决这个问题,在c++11中提供了enable_shared_from_this这个模板类。他提供了一个shared_from_this方法返回自己的智能指针。而这个返回的shared_from_this是基类成员变量,会在返回的过程中增加指针的引用次数。

注意这个shared_from_this()必须要等待构造函数将对象构造完成之后才能返回。

SharedPtr成员变量来自哪里?

出现在RCLCPP_SMART_PTR_DEFINITIONS(Node)
定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#define RCLCPP_SMART_PTR_DEFINITIONS(...) \
RCLCPP_SHARED_PTR_DEFINITIONS(__VA_ARGS__) \
RCLCPP_WEAK_PTR_DEFINITIONS(__VA_ARGS__) \
RCLCPP_UNIQUE_PTR_DEFINITIONS(__VA_ARGS__)

#define RCLCPP_SHARED_PTR_DEFINITIONS(...) \
__RCLCPP_SHARED_PTR_ALIAS(__VA_ARGS__) \
__RCLCPP_MAKE_SHARED_DEFINITION(__VA_ARGS__)


#define __RCLCPP_SHARED_PTR_ALIAS(...) \
using SharedPtr = std::shared_ptr<__VA_ARGS__>; \
using ConstSharedPtr = std::shared_ptr<const __VA_ARGS__>;

这里都是宏定义,最终落在using SharedPtr = std::shared_ptr<__VA_ARGS__>等定义。在这里SharedPtr就是shared_ptr<__VA_ARGS__>的别名。

注意这里的SharedPtr。在看代码的时候经常会发现有如下:
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_;
你就会发现说这个类里边没有定义SharedPtr这个成员变量啊,这个是怎么来的。你去看源码的时候就会发现每一个基类都会有RCLCPP_SMART_PTR_DEFINITIONS(T)这个宏定义,找到最末端就会发现SharedPtr定义在这里,然后还是public成员变量。

这里就留了一个疑问?所有的基类都继承自std::enable_shared_from_this,说明就提供了一个方法shared_from_this()方法返回一个这个对象的指针。那为什么还要重新创建一个SharedPtr对象。

using关键字

在c++11中using的三种用法:
https://blog.csdn.net/shift_wwx/article/details/78742459

  1. 命名空间的使用
  2. 在子类中引用基类的成员
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class T5Base {
    public:
    T5Base() :value(55) {}
    virtual ~T5Base() {}
    void test1() { cout << "T5Base test1..." << endl; }
    protected:
    int value;
    };

    class T5Derived : private T5Base { //私有继承,成员变量在子类中全部转为private.。那么在public中如何访问基类成员。
    public:
    //using T5Base::test1;
    //using T5Base::value;
    void test2() { cout << "value is " << value << endl; }
    };
    ————————————————
    版权声明:本文为CSDN博主「私房菜」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/shift_wwx/article/details/78742459
  3. 别名指定,类似于typedef。相比于typedef,using很容易区分一个别名(尤其是函数指针)。

删除拷贝构造函数

1
2
3
#define RCLCPP_DISABLE_COPY	(...)	
__VA_ARGS__(const __VA_ARGS__ &) = delete; \
__VA_ARGS__ & operator=(const __VA_ARGS__ &) = delete;

C++11则使用delete关键字显式指示编译器不生成函数的默认版本。
https://blog.csdn.net/u012333003/article/details/25299939

__attribute__

__attribute__可以设置函数属性(FunctionAttribute)、变量属性(Variable Attribute)和类型属性(Type Attribute). 函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。也很容易同非GNU应用程序做到兼容之功效。

出现在RCLCPP_PUBLIC等定义中(visibility_control.hpp) https://gcc.gnu.org/wiki/Visibility
语法格式为__attribute__ ((attribute-list))

在C语言中,可以使用static关键字限制符号(函数或变量)只对当前的文件可见,即,static对符号的限制在单个文件级别。而共享库(或动态库)可能包含一个或多个文件,如何将符号限制在库文件(模块)的级别,大多数链接器提供了将一个模块的所有符号进行隐藏或导出的方法,但这样对符号的控制会缺乏灵活性,因此,还有一些额外的工作需要我们来处理。(在GCC 4.0下)

https://blog.csdn.net/zdragon2002/article/details/6061962
https://blog.csdn.net/delphiwcdj/article/details/45225889

在编译一个大型的动态链接库的时候,选择不同的链接方式-fvisibility=default 可以决定动态链接库向外暴露哪些接口。
而使用宏定义来实现visibility可以方便代码在不同平台上迁移。

所以在(visibility_control.hpp)中进行如下定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#if defined _WIN32 || defined __CYGWIN__ //针对win平台
#ifdef __GNUC__
#define RCLCPP_EXPORT __attribute__ ((dllexport))
#define RCLCPP_IMPORT __attribute__ ((dllimport))
#else
#define RCLCPP_EXPORT __declspec(dllexport)
#define RCLCPP_IMPORT __declspec(dllimport)
#endif
#ifdef RCLCPP_BUILDING_LIBRARY
#define RCLCPP_PUBLIC RCLCPP_EXPORT
#else
#define RCLCPP_PUBLIC RCLCPP_IMPORT
#endif
#define RCLCPP_PUBLIC_TYPE RCLCPP_PUBLIC
#define RCLCPP_LOCAL
#else //针对Linux平台
#define RCLCPP_EXPORT __attribute__ ((visibility("default"))) //设置全部可见,链接库接口暴露
#define RCLCPP_IMPORT
#if __GNUC__ >= 4 //编译器GCC是4.0版本以上
#define RCLCPP_PUBLIC __attribute__ ((visibility("default")))
#define RCLCPP_LOCAL __attribute__ ((visibility("hidden"))) //连接库接口隐藏
#else //GCC4.0版本一下不支持visibility control,那就只能全部暴露出去了(因为代码里应该也没有相关的static声明)
#define RCLCPP_PUBLIC
#define RCLCPP_LOCAL
#endif
#define RCLCPP_PUBLIC_TYPE
#endif

explicit

在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。
也就是说如果一个构造函数被声明了explicit,那么不能隐式的转换成拷贝构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Circle 
{
public:
Circle(double r) : R(r) {}
Circle(int x, int y = 0) : X(x), Y(y) {}
Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {}
private:
double R;
int X;
int Y;
};

int _tmain(int argc, _TCHAR* argv[])
{
//发生隐式类型转换
//编译器会将它变成如下代码
//tmp = Circle(1.23)
//Circle A(tmp);
//tmp.~Circle();
Circle A = 1.23;
//如果构造函数变成:
// explicit Circle(double r) : R(r) {}
// 那个上边这一句就会报错,不能隐式转化拷贝构造函数。

//注意是int型的,调用的是Circle(int x, int y = 0)
//它虽然有2个参数,但后一个有默认值,任然能发生隐式转换
Circle B = 123;
//这个算隐式调用了拷贝构造函数
Circle C = A;

return 0;
}

创建pubsub实例

创建pkg

  1. 创建workspace ~/ros2_ws/src
  2. ~/ros2_ws/src下创建pkg
    1
    2
    3
    ros2 pkg create --build-type ament_cmake cpp_pubsub
    # 基本方法是 ros2 pkg create --build-type [cmake, ament_cmake] pkg-name
    # --help 访问帮助文档
  3. ~/ros2_ws/src/cpp_pubsub下会创建pkg树
    1
    2
    3
    4
    -include
    -src
    -CMakeLists.txt
    -package.xml
  4. cpp_pubsub/src下创建文件

简单的publisher node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//published_member_function.cpp
#include <chrono>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

/* This example creates a subclass of Node and uses std::bind() to register a
* member function as a callback from the timer. */

class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}

private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
};

//主函数
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}
  1. c++11中std::bind()这个方法定义在中,用来实现函数转发器,广泛用于泛型编程。看一下源码定义:
    1
    2
    3
    4
    template< class F, class... Args >
    /*unspecified*/ bind( F&& f, Args&&... args );
    - f是可调用对象callable,(函数对象、指向函数指针、到函数引用、指向成员函数指针或指向数据成员指针)
    - args是要绑定的参数列表。
  2. 注意在主函数中已经没有显示的创建node(ros1中是显示的创建node handle),而是生成一个MinimalPublisher的对象进入spin。
  3. std::make_shared(Args& …args)。调用class T的构造函数,并返回一个std::shared_ptr,同时use_count+1。

CMakeLists.txt

CMakeLists中要添加dependencies和相关的编译可执行文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

install(TARGETS
talker
DESTINATION lib/${PROJECT_NAME})

ament_package()

package.xml

1
2
<exec_depend>rclcpp</exec_depend>
<exec_depend>std_msgs</exec_depend>

编译publisher node

  1. ros2_ws/下编译
  2. colcon build 编译ros2_ws/src下的所有pkg
  3. colcon build --packages-select <pkg—name> 编译单独的pkg
  4. 目前(2020.3)还没有提供类似于catkin clean这样的清理编译空间的命令,想要清理的话只能手动删除install, log, build文件
  5. 编译得到的可执行文件在/build/cpp_pubsub/talker,可以直接执行可执行文件,不用使用ros2 run
  6. 如果要使用ros2,和ros1一样要执行环境变量操作source ros2_ws/install/setup.bash。 查看ros2寻址的所有pkg ros2 pkg list
  7. 运行ros2 run cpp_pubsub talker

同样的subscription node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//subscribe_member_function.cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}

private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}

需要注意的是不管是publisher还是subscription的构造过程中,QoS不能省了。顶层的QoS可以理解为data stream buffer,在ros1中也有相关的定义,但是因为ros2的底层是DDS,所以QoS必须定义。

std::placeholder::_1一个占位符,为什么要使用这个?
std::bind()绑定类内成员函数:

  1. 绑定的成员函数不能有重载,bind函数只能通过函数名来识别函数
  2. 绑定类内成员函数,首先要传递对象指针this,然后通过占位符_1来添加传递的参数,这里也就是msg。
  3. bind本身是一种延迟计算的思想。
    那么这里就涉及到这个callback到底什么时候在调用呢。
  4. std::bind()返回的是一个函数未指定类型的函数对象,具体返回什么类型决定于可调用函数f。