• 学校ROS培训项目记录


    项目内容:

    1. 在linux上安装ROS(可以使用虚拟机)
    2. 编写ROS程序,实现话题的订阅和发布
    3. 基于松灵小车硬件,显示雷达数据、图像数据
    4. 移动松灵小车,绘制小车的运动轨迹

    实现过程记录

    1.ubuntu22.04上安装ros2

    ubuntu22.04 安装ros操作系统(但这个方法在执行的時候出錯)
    官方网站讲解手动安装(这个也是一直不出现opt/ros文件夹)

    于是重新创建了一个ubuntu,成功了。。。。我真的不理解。FINE ubuntu22.04安装ROS2 详细教程

    2.ros官方说明文档

    ros1的文档
    ros2的英文文档
    ros2的中文文档 by鱼香ros

    3.ros教学

    动手学ROS2

    4.ROS2节点相关CLI

    • ros2 run :运行可执行文件。eg:要运行turtlesim,请打开一个新终端,然后输入以下命令ros2 run turtlesim turtlesim_node

    • ros2 node list查找正在运行的节点。

    • ros2 node info :与该节点交互的订阅者、发布者、服务和动作 (ROS图连接) 的列表。

    • eg:ros2 run turtlesim turtlesim_node --ros-args --remap __node:=my_turtle重新映射到my_turtle节点上

    5.ROS2中的工作空间

    一个工作空间里面有若干功能包,一个功能包有若干个节点。这个工作空间里面有一个src目录:

    mkdir -p ~/code/ros/turtle_ws/src
    cd ~/code/ros/turtle_ws/dev_ws/src
    
    • 1
    • 2

    6.功能包

    功能包的两种获取方式:

    • 安装获取:sudo apt install ros--package_name。eg:在我的ubuntu上为:sudo apt install ros-humble-turtlesim
    • 手动编译:下载源码然后进行编译生成相关文件。

    功能包相关指令:
    -ros2 create --build-type {cmake,ament_cmake,ament_pythob选一个编译类型} --dependencies <依赖名字>:创建功能包 。
    -ros2 pkg executables [功能包]:列出所有功能包或者某个功能包的可执行文件。
    -res2 pkg list:列出所有的包。
    -ros2 pkg prefix :列出某个功能包的前缀。

    • ros pkg xml :列出包的清单描述文件。

    7.Colcon

    功能包的构建工具,编译代码。

    • 安装:sudo apt-get install python3-colcon-common-extensions在这里插入图片描述

    8.克隆功能包并使用colcon编译

    • 创建工作空间:mkdir -p turtle_ws/src cd turtle_ws/src
    • 下载源码功能包到工作空间的src文件夹下:sudo git clone https://github.com/ros2/examples.git src/examples -b humble
    • 进入工作空间上一级编译:cd ..sudo colcon build
    • source空间:echo "source ~/Documents/code/turtle_ws/install/setup.bash" >> ~/.bashrc~/.bashrc
    • 进入turtle_ws工作空间,启动订阅者者:ros2 run examples_rclcpp_minimal_subscriber subscriber_member_function
    • 启动发布者:ros2 run examples_rclcpp_minimal_publisher publisher_member_function
    • 查看节点数量:ros2 node list
      在这里插入图片描述

    9.面向过程POP编写一个python节点

    • 创建工作空间,并进入。在工作空间中执行code ./打开vscode进行代码编写。
    • 创建一个功能包:ros2 pkg create village_li --build-type ament_python --dependencies rclpy如果在创建中,出现如下报错,请将src目录的权限改为可写sudo chmod 777 src
      在这里插入图片描述
    • 创建节点文件:在__init__.py同级别目录下创建一个叫做li4_pop.py的文件
    • 编写节点代码:
    import rclpy
    from rclpy.node import Node
    
    def main(args=None):
        """
        ros2运行该节点的入口函数
        编写ROS2节点的一般步骤
        1. 导入库文件
        2. 初始化客户端库
        3. 新建节点对象
        4. spin循环节点
        5. 关闭客户端库
        """
        rclpy.init(args=args) # 初始化rclpy
        node = Node("li4")  # 新建一个节点
        node.get_logger().info("大家好,我是作家li4.")
        rclpy.spin(node) # 保持节点运行,检测是否收到退出指令(Ctrl+C)
        rclpy.shutdown() # 关闭rclpy
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    rclpy是什么库?看看GPT如何回答:在这里插入图片描述

    • 接着修改setup.py,增加一句话,告诉ros2村庄来了一位新村民李四,要找这位村民去village_li.li4_pop:main路径下寻找:
     entry_points={
            'console_scripts': [
                "li4_pop_node = village_li.li4_pop:main"
            ],
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 编译运行节点:打开vscode终端,进入town_ws。sudo colcon buildsource install/setup.bashros2 run village_li li4_pop_noderos2 node list
      你的每一次修改之后激动重新执行上面这项的几个指令。

    9.面向对象OOP编写一个python节点

    • 创建节点文件:在__init__.py同级别目录下创建一个叫做li4_oop.py的文件
    • 编写节点代码:
    #!/usr/bin/env python3
    import rclpy
    from rclpy.node import Node
    
    
    class WriterNode(Node):
        """
        创建一个作家节点,并在初始化时输出一个话
        """
        def __init__(self,name):
            super().__init__(name)
            self.get_logger().info("大家好,我是%s,我是一名作家!" % name)
    
    
    def main(args=None):
        """
        ros2运行该节点的入口函数
        1. 导入库文件
        2. 初始化客户端库
        3. 新建节点
        4. spin循环节点
        5. 关闭客户端库
        """
        rclpy.init(args=args) # 初始化rclpy
        node = WriterNode("li4")  # 新建一个节点
        rclpy.spin(node) # 保持节点运行,检测是否收到退出指令(Ctrl+C)
        rclpy.shutdown() # 关闭rclpy
    
    
    • 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
    • 接着修改setup.py,增加一句话,告诉ros2村庄来了一位新村民李四,要找这位村民去village_li.li4_oop:main路径下寻找:
     entry_points={
            'console_scripts': [
                "li4_oop_node = village_li.li4_oop:main"
            ],
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 编译运行节点:打开vscode终端,进入town_ws。sudo colcon buildsource install/setup.bashros2 run village_li li4_oop_noderos2 node list

    10.以POP创建CPP功能包和节点

    • 创建王家村功能包,src目录下执行:ros2 pkg create village_wang --build-type ament_cmake --dependencies rclcpp
    • 接着在village_wang/src下创建一个wang2.cpp文件
    • wang2.cpp中编写代码:
    #include "rclcpp/rclcpp.hpp"
    
    
    int main(int argc, char **argv)
    {
        rclcpp::init(argc, argv);
        /*产生一个Wang2的节点*/
        auto node = std::make_shared<rclcpp::Node>("wang2");
        // 打印一句自我介绍
        RCLCPP_INFO(node->get_logger(), "大家好,我是单身狗wang2.");
        /* 运行节点,并检测退出信号*/
        rclcpp::spin(node);
        rclcpp::shutdown();
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 添加到CmakeLists最后一行:
    //添加这两行代码的目的是让编译器编译wang2.cpp这个文件,不然不会主动编译。
    add_executable(wang2_node src/wang2.cpp)
    ament_target_dependencies(wang2_node rclcpp)
    //需要手动将编译好的文件安装到install/village_wang/lib/village_wang下
    install(TARGETS
      wang2_node
      DESTINATION lib/${PROJECT_NAME}
    )
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 编译运行节点:打开vscode终端,进入town_ws。sudo colcon buildsource install/setup.bashros2 run village_wang wang2_noderos2 node list

    11.以OOP方式创建CPP

    #include "rclcpp/rclcpp.hpp"
    
    /*
        创建一个类节点,名字叫做SingleDogNode,继承自Node.
    */
    class SingleDogNode : public rclcpp::Node
    {
    
    public:
        // 构造函数,有一个参数为节点名称
        SingleDogNode(std::string name) : Node(name)
        {
            // 打印一句自我介绍
            RCLCPP_INFO(this->get_logger(), "大家好,我是单身狗%s.",name.c_str());
        }
    
    private:
       
    };
    
    int main(int argc, char **argv)
    {
        rclcpp::init(argc, argv);
        /*产生一个Wang2的节点*/
        auto node = std::make_shared<SingleDogNode>("wang2");
        /* 运行节点,并检测退出信号*/
        rclcpp::spin(node);
        rclcpp::shutdown();
        return 0;
    }
    
    • 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

    11.ROS2通信机制话题

    11.1 话题的规则
    • 话题名字是关键,发布订阅接口类型要相同,发布的是字符串,接受也要用字符串来接收;
    • 同一个人(节点)可以订阅多个话题,同时也可以发布多个话题,就像一本书的作者也可以是另外一本书的读者;
    • 同一个小说不能有多个作者(版权问题),但跟小说不一样,同一个话题可以有多个发布者。
    11.2 RQT工具之rqt_gragh

    可以通过命令来看到节点和节点之间的数据关系的。

    ros2 run demo_nodes_py listener
    ros2 run demo_nodes_cpp talker
    rqt_graph
    
    • 1
    • 2
    • 3
    11.3 ROS2话题相关命令行界面(CLI)工具
    • ros2 topic -h:topic帮助命令
    • ros2 topic list:返回系统中当前活动的所有主题的列表
    • ros2 topic list -t:增加消息类型
    • ros2 topic echo /<话题名>:打印实时话题内容
    • ros2 topic info /<话题名>:查看主题信息
    • ros2 interface show <某个话题的type名>:查看消息类型
    • ros2 topic pub /<话题名> <话题的type名> 'data: "xxx"':手动发布命令

    12.python编写话题发布和订阅

    • 进入空间的src目录执行: ros2 pkg create py_pubsub --build-type ament_python --dependencies rclpy
    • 在与__init__.py同级目录下创建一个publisher.py和subscriber.py文件
    • 发布者代码如下:
    import rclpy
    from rclpy.node import Node
    from std_msgs.msg import String
    
    class WriterNode(Node):
        def __init__(self,name):
            super().__init__(name)
            self.name=name
            self.get_logger().info("I am a publisher, my name is %s"%name)
            self.publisher=self.create_publisher(String,"TOPIC",10)
    
            self.i=0
            self.timer_period=5
            self.timer=self.create_timer(self.timer_period,self.time_callback)
        
        def time_callback(self):
            msg=String()
            container="qwertyuiopasdfghjklzxcvbnm-=[];',./"
            msg.data=f"{container[self.i%34]}"
            self.get_logger().info(f"This is my {self.i} times publish the message for you. And please save the message below:{msg.data}") 
            self.publisher.publish(msg)
            self.i+=1
    
    
    def main(args=None):
        rclpy.init(args=args)
        node=WriterNode("wjy")
        rclpy.spin(node)
        node.destroy_node()
        rclpy.shutdown()
     
    # 鱼香ROS的代码
    # import rclpy
    # from rclpy.node import Node
    
    # """
    # 导入消息类型
    
    # 声明并创建发布者
    
    # 编写发布逻辑发布数据 
    # """
    # from std_msgs.msg import String
    
    # class WriterNode(Node):
    #     """
    #     创建一个李四节点,并在初始化时输出一个话
    #     """
    #     def __init__(self,name):
    #         super().__init__(name)
    #         self.get_logger().info("大家好,我是%s,我是一名作家!" % name)
    #         # 2.创建并初始化发布者成员属性pubnovel
    #         self.pub_novel = self.create_publisher(String,"sexy_girl", 10) 
    #         #create_publisher has 3 params:method type,topic name,message length
    #         # use this cli to see all the message type in std_msg   :   ros2 interface package std_msgs
    #         # use this cli to see all the message type in ros2   :   ros2 interface list
    
    
    #         #3. 编写发布逻辑
    #         # 创建定时器成员属性timer
    #         self.i = 0 # i 是个计数器,用来算章节编号的
    #         timer_period = 5  #每5s写一章节话
    #         self.timer = self.create_timer(timer_period, self.timer_callback)  #启动一个定时装置,每 timer_period s,调用一次time_callback函数
    
    
    #     def timer_callback(self):
    #         """
    #         定时器回调函数
    #         """
    #         msg = String()
    #         msg.data = '第%d回:潋滟湖 %d 次偶遇胡艳娘' % (self.i,self.i)
    #         self.pub_novel.publish(msg)  #将小说内容发布出去
    #         self.get_logger().info('李四:我发布了艳娘传奇:"%s"' % msg.data)    #打印一下发布的数据,供我们看
    #         self.i += 1 #章节编号+1
    
    # def main(args=None):
    #     """
    #     ros2运行该节点的入口函数
    #     1. 导入库文件
    #     2. 初始化客户端库
    #     3. 新建节点
    #     4. spin循环节点
    #     5. 关闭客户端库
    #     """
    #     rclpy.init(args=args) # 初始化rclpy
    #     node = WriterNode("publisher")  # 新建一个节点
    #     rclpy.spin(node) # 保持节点运行,检测是否收到退出指令(Ctrl+C)
    #     rclpy.shutdown() # 关闭rclpy
    
    • 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
    • 订阅者代码如下:
    import rclpy
    from rclpy.node import Node
    from std_msgs.msg import String
    
    class ReceiveNode(Node):
        def __init__(self,name):
            super().__init__(name)
            self.get_logger().info(f"Hi, i am a subscriber, my name is {name}.")
            self.subscription=self.create_subscription(String,"TOPIC",self.listener_callback,10)
            # self.subscription #prevent unused variable warning
        
        def listener_callback(self,msg):
            print(msg)
            self.get_logger().info(f"I have already receive the message:{msg.data}")
    
    
    def main(args=None):
        rclpy.init(args=args)
        node=ReceiveNode("Larissa")
        rclpy.spin(node)
        node.destroy_node()
        rclpy.shutdown()
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 还可以将发布订阅者写在一起,新建一个mergepubsub.py(代码来源于鱼香ROS):
    #!/usr/bin/env python3
    import rclpy
    from rclpy.node import Node
    # 导入话题消息类型
    from std_msgs.msg import String,UInt32
    
    class WriterNode(Node):
        """
        创建一个李四节点,并在初始化时输出一个话
        """
        def __init__(self,name):
            super().__init__(name)
            self.get_logger().info("大家好,我是%s,我是一名作家!" % name)
            # 创建并初始化发布者成员属性pubnovel
            self.pubnovel = self.create_publisher(String,"sexy_girl", 10) 
    
    
            # 创建定时器成员属性timer
            self.i = 0 # i 是个计数器,用来算章节编号的
            timer_period = 5  #每5s写一章节话
            self.timer = self.create_timer(timer_period, self.timer_callback)  #启动一个定时装置,每 1 s,调用一次time_callback函数
    
    
            # 账户钱的数量
            self.account = 80
            # 创建并初始化订阅者成员属性submoney
            self.submoney = self.create_subscription(UInt32,"sexy_girl_money",self.recv_money_callback,10)
            
    
        def timer_callback(self):
            """
            定时器回调函数
            """
            msg = String()
            msg.data = '第%d回:潋滟湖 %d 次偶遇胡艳娘' % (self.i,self.i)
            self.pubnovel.publish(msg)  #将小说内容发布出去
            self.get_logger().info('李四:我发布了艳娘传奇:"%s"' % msg.data)    #打印一下发布的数据,供我们看
            self.i += 1 #章节编号+1
    
    
        def recv_money_callback(self,money):
            """
            4. 编写订阅回调处理逻辑
            """
            self.account += money.data
            self.get_logger().info('李四:我已经收到了%d的稿费' % self.account)
    
    
    def main(args=None):
        """
        ros2运行该节点的入口函数,可配置函数名称
        """
        rclpy.init(args=args) # 初始化rclpy
        node = WriterNode("li4")  # 新建一个节点
        rclpy.spin(node) # 保持节点运行,检测是否收到退出指令(Ctrl+C)
        rclpy.shutdown() # rcl关闭
    
    
    • 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
    • 同样别忘记在setup.py中添加代码:
     entry_points={
            'console_scripts': [
                "publisher_node=py_pubsub.publisher:main",
                "subscriber_node=py_pubsub.subscriber:main",
                "mergepubsub_node=py_pubsub.mergepubsub:main",
            ],
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    7年测试工程师分享的20K的测试“卷王真经”
    尚医通 (六) --------- 集成 Swagger
    微信小程序自定义顶部导航栏
    OpenCV(二十五):边缘检测(一)
    自己动手写PBR
    做3D建模的女生多吗?揭露行业比列
    什么样的触达方式,会员会喜欢?
    第 46 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(昆明),签到题3题
    数据挖掘--认识数据
    CentOS 7 下将 MySQL 5.6 升级为MySQL 5.7
  • 原文地址:https://blog.csdn.net/Wu_JingYi0829/article/details/132700938