1.节点
ROS2中每一个节点也是只负责一个单独的模块化的功能
通信方式:话题-topics 服务-services 动作-Action 参数-parameters
ROS的目录文件结构:
节点(node)、消息(message)、主题(topic)、服务(service),ROS控制器(ROS master)
-
1.在ROS中,最小的进程单元就是节点(node) 一个软件包里有多个可执行文件,可执行文件在运行之后就成了一个进程(progress),这个进程在ROS中就叫节点。
-
2.从程序角度来说,node就是一个可执行文件(通常为C++生成的可执行文件、Python脚本)被执行,加载到了内存之中
-
3.从功能角度来说,通常一个node负责机器人的某一个单独的功能,由于机器人的功能模块非常复杂,我们往往不会把所有的功能都集中在同一个node,而是采用分布式的方式,把鸡蛋放到不同的篮子里。
-
4.节点之间通过话题(Topic)、服务(Service)、动作(Actions)或者参数(Parameter)实现数据的收发。
2.ROS2系统架构
-
1.操作系统层
-
2.DDS实现层(封装DDS-数据分发服务)实现节点间通信
-
3.抽象DDS层-RWM(进一步封装)
-
4.ROS客户端RCL(ROS的一种API,提供了对ROS话题、服务、参数、Action等接口)
基于rmw实现了rclc,有了rclc,我们就可以实现各个语言的库
-
5.应用层 代码
3.克隆源码
git clone https://github.com/ros2/examples src/examples -b foxy
//-b 为Branchs 分支 选择安装的版本 foxy
4.工作空间与功能包
工作空间是包含若干个功能包的目录,一开始大家把工作空间理解成一个文件夹就行了。这个文件夹包含下有src
,功能包可以理解为存放节点的地方
cd d2lros2/chapt2/
mkdir -p chapt2_ws/src
5.功能包获取的两种方式
1.安装获取 2.手动编译
手动编译相对麻烦一些,需要下载源码然后进行编译生成相关文件,手动编译之后,需要手动source工作空间的install目录
ros2 pkg create <package-name> --build-type {cmake,ament_cmake,ament_python} --dependencies <依赖名字> //手动创建
cmake创建后会有4个文件 include src CMakeLists .xml文件 每个包对应一个cmake文件
6.编译功能包
源码下载在src文件后编译 colcon build
编译后在src(功能包)同级目录下会生成 bulid(编译文件)、install(可执行文件)、log(日志文件)
-
•
build
目录存储的是中间文件。对于每个包,将创建一个子文件夹,在其中调用例如CMake -
•
install
目录是每个软件包将安装到的位置。默认情况下,每个包都将安装到单独的子目录中。 -
•
log
目录包含有关每个colcon调用的各种日志信息
7.运行自己的节点
1.colcon build
之后需要source install/setup.bash
一下资源
2.运行节点ros2 run examples_rclcpp_minimal_subscriber subscriber_member_function
在install里面
3.打开新的终端
source install/setup.bash ros2 run examples_rclcpp_minimal_publisher publisher_member_function
-
•关于COLCON的其他使用
colcon build --packages-select YOUR_PKG_NAME //只编译一个包
colcon build --symlink-install//允许通过更改src下的部分文件来改变install(重要)
//每次调整 python 脚本时都不必重新build了
当我们colcon bulid 之后src和install就没有联系了,如果改src文件继续运行可执行文件还是旧版本的,还需再次colcon bulid. 如果是py代码,py代码是从src内直接拷贝到install文件中,不是链接过去,改后colcon build也不行,此时需要colcon build --symlink-install
链接过去。
8.修改setup.py
li4_node=villager_li.li4:main//这里li4其实是li4.py(py省略)
注意编译要回到工作空间内 不要在src内部进行colocon
9.快捷键
ctrl +Alt/ ctrl+F切换虚拟机
ctrl+shift+5 在vscode里面再开一个终端
ctrl+shift+p打开C/C++配置文件
10.py创建节点
-
1.创建工作空间_ws,并创建src文件
-
2.创建功能包
-
3.
cd chapt2/chapt2_ws/src/ ros2 pkg create example_py --build-type ament_python --dependencies rclpy
-
4.在功能包同名文件夹内创建节点文件写代码
-
5.
导入库文件 初始化客户端库 新建节点 spin循环节点 关闭客户端库
-
6.接着修改 setup.py
-
7.
"li4_node=villagerli.li4:main" 在villsge_li功能包下li4.py文件的main程序中
-
8.进入工作空间 编译colcon build 后source install/setup.bash
-
9.查看功能包
ros2 pkg list |grep vill
-
10.运行功能包节点
ros2 run village_li
li4 -
11.在新终端检测节点
ros2 node list ros2 node list info /li4
重新修改代码需要重新编译colcon build即可
11.CPP创建节点 (注意生成的node节点名字app不要随意改)
-
1.#include "rclcpp/rclcpp.hpp"当出现未包含路径时去配置路径 /opt/ros/foxy/**
-
2.
cd chapt2_ws/src
ros2 pkg create example_cpp --build-type ament_cmake --dependencies rclcpp
//node_01.cpp #include "rclcpp/rclcpp.hpp" class node_01:public rclcpp::Node { private: public: node_01(std::string name):Node(name) { RCLCPP_INFO(this->get_logger(),"大家好5"); } }; int main(int argc,char **argv) { rclcpp::init(argc,argv); auto node = std::make_shared<node_01>("mynode1");//这只是显示用的名字 rclcpp::spin(node); rclcpp::shutdown(); return 0; }
-
3.编写完成后Cmakelist修改
ament_package() add_executable(app src/node_01.cpp)//源文件生成可执行文件app node_01就是建的文件 ament_target_dependencies(app rclcpp)//可执行文件的依赖 install(TARGETS app//app文件的地址 (注意每改一次可执行文件的名字都会在lib文件中重新生成一个 旧的还在) DESTINATION lib/${PROJECT_NAME} )
//1. colcon build --packgets-select example_cpp(包名字) //2. source install/setup.bash //3. ros2 run example_cpp app(节点) //4. ros2 pkg list |grep village(如果找不到 就source一下) // 1. ros2 run example_cpp //5. ros2 node list info /example_cpp
//面向过程
int main(int argc, char **argv)
{
/* 初始化rclcpp */
rclcpp::init(argc, argv);
/*产生一个wang2的节点*/
auto node = std::make_shared<rclcpp::Node>("wang2");//这个wang2就是节点名字(只是起一个名字)
// 打印一句自我介绍
RCLCPP_INFO(node->get_logger(), "我是wang2.");
/* 运行节点,并检测退出信号 Ctrl+C*/
rclcpp::spin(node);
/* 停止运行 */
rclcpp::shutdown();
return 0;
}
12.rqt_graph 话题工具 ROS2话题入门
ros2 topic list -t //看目前有哪个话题
ros2 topic echo /chatter //打印内容
ros2 topic info /chatter //查看话题信息
13.话题
ros2 run demo_nodes_py listener
ros2 run demo_nodes_cpp talker
rqt_graph
ros2 topic -h
ros2 topic list
ros2 topic list -t
ros2 topic echo /chatter
ros2 topic info /chatter
ros2 interface show std_msgs/msg/String
ros2 topic pub /chatter std_msgs/msg/String 'data: "123"'
14.接口
-
1.
ros2 interface package sensor_msgs //命令可以查看传感器类接口包下所有的接口
-
2.除了参数之外,话题、服务和动作(Action)都支持自定义接口。
话题接口xxx.msg、服务接口xxx.srv、动作接口xxx.action
-
3.接口数据类型:基础类型和包装类型
1.新建工作空间 创建接口功能包和 其他功能包处于同一级目录
2.新建msg文件夹和Novel.msg(N大写)
3.编写Novel.msg内容
//1.在src目录下创建 ros2 pkg creat village_interfaces (--build type ament_cmake --dependencies rclcpp)
//2.#注意Novel.msg 的Novel 要大写 在src文件中创建msg文件夹 创建Novel.msg
//#原始数据类型(9种)
string content
//#调用已有的消息类型 ros2 interface package sensor_msgs
sensor_msgs/Image image
3.修改CmakeLists
find_package(sensor_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
//声明msg文件所属工程名字、文件位置以及依赖
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Novel.msg"
DEPENDENCIES sensor_msgs//(添加包的依赖)
)
4.修改xml
<depend>sensor_msgs</depend>
//以下为必须项 分别为构建时依赖项、运行时依赖项和分组标签
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class TopicPublisher01 : public rclcpp::Node
{
public:
// 构造函数,有一个参数为节点名称
TopicPublisher01(std::string name) : Node(name)
{
RCLCPP_INFO(this->get_logger(), "大家好,我是%s.", name.c_str());
// 创建发布者
command_publisher_ = this->create_publisher<std_msgs::msg::String>("command", 10);
// 创建定时器,500ms为周期,定时发布
timer_ = this->create_wall_timer(std::chrono::milliseconds(500), std::bind(&TopicPublisher01::timer_callback, this));
}
private:
void timer_callback()
{
// 创建消息
std_msgs::msg::String message;
message.data = "forward";
// 日志打印
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
// 发布消息
command_publisher_->publish(message);
}
// 声名定时器指针
rclcpp::TimerBase::SharedPtr timer_;
// 声明话题发布者指针
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
};
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class TopicSubscribe01 : public rclcpp::Node
{
public:
TopicSubscribe01(std::string name) : Node(name)
{
RCLCPP_INFO(this->get_logger(), "大家好,我是%s.", name.c_str());
// 创建一个订阅者订阅话题
command_subscribe_ = this->create_subscription<std_msgs::msg::String>("command", 10, std::bind(&TopicSubscribe01::command_callback, this, std::placeholders::_1));
}
private:
// 声明一个订阅者
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr command_subscribe_;
// 收到话题数据的回调函数
void command_callback(const std_msgs::msg::String::SharedPtr msg)
{
double speed = 0.0f;
if(msg->data == "forward")
{
speed = 0.2f;
}
RCLCPP_INFO(this->get_logger(), "收到[%s]指令,发送速度 %f", msg->data.c_str(),speed);
}
};
15.服务
ros2 service list
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"
ros2 service type /add_two_ints
ros2 service find example_interfaces/srv/AddTwoInts
1.创建服务端
#include "example_interfaces/srv/add_two_ints.hpp"
#include "rclcpp/rclcpp.hpp"
class ServiceServer01 : public rclcpp::Node {
public:
ServiceServer01(std::string name) : Node(name) {
RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
// 创建服务
add_ints_server_ =this->create_service<example_interfaces::srv::AddTwoInts>(
"add_two_ints_srv",std::bind(&ServiceServer01::handle_add_two_ints, this,std::placeholders::_1, std::placeholders::_2));
}//服务名称 、回调函数(2个参数做占位符)、后面2个默认参数
private:
// 声明一个服务
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr
add_ints_server_;
// 收到请求的处理函数
void handle_add_two_ints(
const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response) {
RCLCPP_INFO(this->get_logger(), "收到a: %ld b: %ld", request->a,
request->b);
response->sum = request->a + request->b;
};
};
2.创建客户端
#include "example_interfaces/srv/add_two_ints.hpp"
class ServiceClient01 : public rclcpp::Node {
public:
// 构造函数,有一个参数为节点名称
ServiceClient01(std::string name) : Node(name) {
RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
// 创建客户端
client_ = this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints_srv");
}
void send_request(int a, int b) {
RCLCPP_INFO(this->get_logger(), "计算%d+%d", a, b);
// 1.等待服务端上线
while (!client_->wait_for_service(std::chrono::seconds(1))) {
//等待时检测rclcpp的状态
if (!rclcpp::ok()) {
RCLCPP_ERROR(this->get_logger(), "等待服务的过程中被打断...");
return;
}
RCLCPP_INFO(this->get_logger(), "等待服务端上线中");
}
// 2.构造请求的
auto request =
std::make_shared<example_interfaces::srv::AddTwoInts_Request>();
request->a = a;
request->b = b;
// 3.发送异步请求,然后等待返回,返回时调用回调函数
client_->async_send_request(
request, std::bind(&ServiceClient01::result_callback_, this,
std::placeholders::_1));
};
private:
// 声明客户端
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;
void result_callback_(
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedFuture
result_future) {
auto response = result_future.get();
RCLCPP_INFO(this->get_logger(), "计算结果:%ld", response->sum);
}
};
16.遍历自定义接口数组类型变量
for (const auto &node_status : proStatus->nodes_status) {
RCLCPP_INFO(this->get_logger(), " Node Name: %s",node_status.node_name.c_str());
RCLCPP_INFO(this->get_logger(), "Node Status:%d",node_status.status);
}//“foreach”循环。它用于遍历容器中的元素
遍历 proStatus->nodes_status
容器中的每一个 NodeStatus
元素,每次循环迭代都将当前元素的值赋给 node_status
,然后执行循环体中的代码