ROS2常用命令及基本开发学习内容

1.节点

ROS2中每一个节点也是只负责一个单独的模块化的功能

通信方式:话题-topics 服务-services 动作-Action 参数-parameters

ROS的目录文件结构:

节点(node)、消息(message)、主题(topic)、服务(service),ROS控制器(ROS master)

  1. 1.在ROS中,最小的进程单元就是节点(node) 一个软件包里有多个可执行文件,可执行文件在运行之后就成了一个进程(progress),这个进程在ROS中就叫节点。

  2. 2.从程序角度来说,node就是一个可执行文件(通常为C++生成的可执行文件、Python脚本)被执行,加载到了内存之中

  3. 3.从功能角度来说,通常一个node负责机器人的某一个单独的功能,由于机器人的功能模块非常复杂,我们往往不会把所有的功能都集中在同一个node,而是采用分布式的方式,把鸡蛋放到不同的篮子里。

  4. 4.节点之间通过话题(Topic)、服务(Service)、动作(Actions)或者参数(Parameter)实现数据的收发。

2.ROS2系统架构

  1. 1.操作系统层

  2. 2.DDS实现层(封装DDS-数据分发服务)实现节点间通信

  3. 3.抽象DDS层-RWM(进一步封装)

  4. 4.ROS客户端RCL(ROS的一种API,提供了对ROS话题、服务、参数、Action等接口)

    基于rmw实现了rclc,有了rclc,我们就可以实现各个语言的库

  5. 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. 1.创建工作空间_ws,并创建src文件

  2. 2.创建功能包

  3. 3.cd chapt2/chapt2_ws/src/ ros2 pkg create example_py --build-type ament_python --dependencies rclpy

  4. 4.在功能包同名文件夹内创建节点文件写代码

  5. 5.导入库文件 初始化客户端库 新建节点 spin循环节点 关闭客户端库

  6. 6.接着修改 setup.py

  7. 7."li4_node=villagerli.li4:main" 在villsge_li功能包下li4.py文件的main程序中

  8. 8.进入工作空间 编译colcon build 后source install/setup.bash

  9. 9.查看功能包 ros2 pkg list |grep vill

  10. 10.运行功能包节点 ros2 run village_li li4

  11. 11.在新终端检测节点 ros2 node list ros2 node list info /li4

    重新修改代码需要重新编译colcon build即可

11.CPP创建节点 (注意生成的node节点名字app不要随意改)

  1. 1.#include "rclcpp/rclcpp.hpp"当出现未包含路径时去配置路径 /opt/ros/foxy/**

  2. 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. 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. 1.ros2 interface package sensor_msgs //命令可以查看传感器类接口包下所有的接口

  2. 2.除了参数之外,话题、服务和动作(Action)都支持自定义接口。

    话题接口xxx.msg、服务接口xxx.srv、动作接口xxx.action

  3. 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,然后执行循环体中的代码

  • 37
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值