ROS2学习记录——节点
创建功能包
创建Python功能包
# 创建Python功能包
$ ros2 pkg create --build-type ament_python --license Apache-2.0 {PKG_NAME}
创建好功能包之后,生成的目录结构如下图所示。PKG_NAME/PKG_NAME
下为主要开发区域,例如编写了一个python_node.py
的程序。
编写好程序后在setup.py
中进行配置,对需要构建生成的可执行文件进行声明。
entry_points={
'console_scripts': [
'python_node = demo_python_pkg.python_node:main' #exe_name = PKG_NAME.file_name:func_name
],
},
在package.xml
中添加依赖信息,虽然不会影响构建过程,但有助于迅速阅读依赖信息。
<depend>rclpy</depend>
进行构建。
#构建
colcon build
构建完成后,生成相应的功能包,存放在install/
目录下。
在执行可执行文件之前,需要更改环境变量,setup.bash
提供了修改环境变量的脚本,需要注意的是,该脚本需要在一级目录下执行,且修改的环境变量仅在当前终端有效。
# 更改环境变量
$ source install/setup.bash
在修改完环境变量后,就可以运行可执行文件了。
# 运行可执行文件
$ ros2 run demo_python_pkg python_node
创建C++功能包
# 创建C++功能包
$ ros2 pkg create --build-type ament_cmake --license Apache-2.0 {PKG_NAME}
创建功能包后,功能包目录如下图,其中src/
目录存放.cpp
文件,include/
目录存放相应的头文件。
配置CMakeLists
find_package(rclcpp REQUIRED) # 查找对应的头文件和库文件
add_executable(cpp_node src/cpp_node.cpp) # 添加可执行文件cpp_node, 编译路径为src/cpp_node.cpp, 这是相对于CMakeLists的路径
#target_include_directories(cpp_node PUBLIC ${rclcpp_INCLUDE_DIRS})
#target_link_libraries(cpp_node ${rclcpp_LIBRARIES})
ament_target_dependencies(cpp_node rclcpp) #进行包含和链接,替代上面两句语句
install(TARGETS cpp_node
DESTINATION lib/${PROJECT_NAME}
)#将生成的可执行文件cpp_node拷贝至lib/${PROJECT_NAME}下,否则在build目录下
在package.xml
中添加依赖信息,虽然不会影响构建过程,但有助于迅速阅读依赖信息。
<depend>rclcpp</depend>
进行构建,注意,在一级目录终端进行编译,输入以下指令会编译(构建)当前目录下的所有功能包。
$ colcon build
更改环境变量。
$ source install/setup.bash
运行可执行文件。
# 运行可执行文件
$ ros2 run demo_cpp_pkg cpp_node
Workspace—多功能包的最佳实践
创建workspace,命名可自定义。
$ mkdir -p workspace/src
$ mv demo_cpp_pkg workspace/src/
$ mv demo_python_pkg workspacce/src/
$ rm -rf build/ install/ log/
在workspace
中打开集成终端,就可以进行构建了。生成的文件目录在workspace/
下.
进行选择性构建。
$ colcon build --packages-select {PKG_name}
若功能包的构建存在依赖关系,可以将被依赖的功能包添加至对应功能包的package.xml
里。例如,若demo_python_pkg
的构建依赖于demo_cpp_pkg
功能包的构建,那么就可以在demo_python_pkg
功能包的package.xml
中添加依赖。在进行构建后,会优先构建被依赖的功能包。
<depend>demo_cpp_pkg</depend>
创建自定义Node
在ROS2中,提供了自带的Node类,那么就可以通过继承,创建属于自己的Node类。
Python创建
class MyNode(Node):
def __init__(self, node_name_val:str, name_val:str, age_val:int)->None:
super().__init__(node_name_val) # 调用父类__init__
self.name = name_val
self.age = age_val
def eat(self, food:str):
self.get_logger().warn(f"{self.name}喜欢吃{food}")
# 使用自己的创建的类,就可以调用自己定义的属性、方法,以及Node类的属性和反法
rclpy.init()
node = MyNode('my_node', '王五', 23)
node.get_logger().warn(f"{node.name},年龄{node.age}岁")
node.eat('茅台')
rclpy.spin(node)
rclpy.shutdown()
[WARN] [1732333154.004132284] [my_node]: 王五,年龄23岁
[WARN] [1732333154.004452767] [my_node]: 王五喜欢吃茅台
C++创建
class MyNode : public rclcpp::Node
{
private:
std::string name_;
int age_;
public:
MyNode(const std::string &node_name, const std::string name, const int age)
:Node(node_name) { //调用基类构造函数
this->name_ = name;
this->age_ = age;
};
void eat(const std::string food_name) {
RCLCPP_INFO(this->get_logger(), "我叫%s, 今年%d岁, 喜欢吃%s", this->name_.c_str(), this->age_, food_name.c_str());
};
};
ROS2中常用语法
C++共享指针
#include <iostream>
#include <memory>
class Ball
{
public:
Ball() {
std::cout << "The ball appears" << std::endl;
}
~Ball() {
std::cout << "The ball disappears" << std::endl;
}
void Bounce() {
std::cout << "The ball jumps" << std::endl;
}
};
int main() {
std::cout << "Make a shared ptr!" << std::endl;
// 使用 make_shared 会动态分配一块内存, 创建对应的资源,
std::shared_ptr<Ball> sp1 = std::make_shared<Ball>();
std::cout << "sp1.use_count = " << sp1.use_count() << std::endl;
auto sp2 = sp1; //增加引用
std::cout << "sp1.use_count = " << sp1.use_count()
<< ", sp2.use_count = " << sp2.use_count() << std::endl;
std::cout << "Reset the sp1!" << std::endl;
sp1.reset();
std::cout << "Reset the sp2!" << std::endl;
sp2.reset(); //当引用计数为0时,自动释放内存
return 0;
}
#输出
Make a shared ptr!
The ball appears
sp1.use_count = 1
sp1.use_count = 2, sp2.use_count = 2
Reset the sp1!
Reset the sp2!
The ball disappears
共享指针引入了引用计数的机制,当所有引用被reset后,会自动释放其指向的内存,降低手动释放内存带来的负担。共享指针主要语法:
//共享指针的创建
std::shared_ptr<Type> sp1 = std::make_shared<Type>(Initial_Val);
//共享指针的重置
sp.reset();
//获取原始指针
p = sp.get();
C++ Lambda函数
#include <iostream>
#include <algorithm>
/* Lamda函数 */
int main() {
auto add = [](int a, int b) -> int {
return a+b;
};
int sum = add(10, 20);
auto print_sum =[sum]() -> void {
std::cout << "sum = " << sum << std::endl;
};
print_sum();
return 0;
}
auto 函数名= [捕获列表](形参列表) -> 返回值类型 {
...
}
函数包装器
#include <iostream>
#include <functional>
/* 自由函数 */
void free_func(const std::string func_name) {
std::cout << func_name << ":the free function is called!" << std::endl;
}
/* 成员函数 */
class Fun
{
public:
void member_func(const std::string func_name) {
std::cout << func_name << ":the member function is called!" << std::endl;
}
};
/* Lambda函数 */
auto lamda_func = [](const std::string func_name) -> void {
std::cout << func_name << ":the lamda function is called!" << std::endl;
};
int main() {
Fun my_func;
std::function<void(const std::string)> func1 = free_func;
std::function<void(const std::string)> func2 = lamda_func;
std::function<void(const std::string)> func3 = std::bind(&Fun::member_func, &my_func, std::placeholders::_1);
func1("Free_FUN");
func2("LAMDA_FUN");
func3("MRM_FUN");
return 0;
}
注意常见三种函数的包装格式。
C++多线程与回调函数
声明:以上内容均学习于“鱼香ROS”博主系列课程