最近做的项目需要用到ROS,又开始捣鼓ROS啦。仅有单片机基础,让我看看在怎么个事儿。搞机器人就是要不断学习呐~

2.1 通信机制简介 · GitBook课程简介 · GitBook2.1 通信机制简介 · GitBook

目录

一 

准备

ROS2常用命令

 创建节点模板(python):

二、通讯模块

 功能包安装

通讯机制术语

发送文本消息(Python)

订阅消息

自定义接口

 Import自定义接口  


一 

准备

无论是使用C++还是Python编写ROS2程序,都需要依赖于工作空间,在此,我们先实现工作空间的创建与编译,打开终端,输入如下指令:

mkdir -p ws00_helloworld/src #创建工作空间以及子级目录 src,工作空间名称可以自定义
cd ws00_helloworld #进入工作空间
colcon build #编译

上述指令执行完毕,将创建ws00_helloworld目录,且该目录下包含build、install、log、src共四个子级目录。

ROS2常用命令

创建

进入到想要创建文件夹的空间下(cd 文件夹/),创建一个名为pkg03_hellovscode_py的文件夹,里面名为hellovscode的py文件

  • --node-name 参数:在包创建时生成一个默认的节点文件和节点类,提供一个起点。

ros2 pkg create pkg03_hellovscode_py(文件夹名) --build-type ament_python --dependencies rclpy --node-name hellovscode(文件名)

不写--node-name 文件名就后面自己添加,如下: 

单独创建py文件:

 在vscode里右键新建文件 命名.py

添加路径

1.package.xml

在创建功能包时,所依赖的功能包已经自动配置了,配置内容如下:

<depend>rclcpp</depend>
...

2.setup.py

entry_points字段的console_scripts中添加如下内容:

entry_points={
    'console_scripts': [
        '文件名 = 文件夹.文件名:main',
        'demo01_talker_str_py = py01_topic.demo01_talker_str_py:main',  
    ],
},

编译

每次更改完代码后要编译一下哇

colcon build (全部文件都编译)

colcon build --packages-select 文件名 (选中文件编译)

. install/setup.bash

执行

"""
ros2 run 文件夹名 文件名
"""

ros2 run pkg02_helloworld_py helloworld

查找

ros2 pkg --h #获取pkg的帮助命令

ros2 pkg executables 包名 #输出所有功能包下的可执行程序
ros2 pkg list #列出所有功能包
ros2 pkg prefix 包名 #列出功能包路径
ros2 pkg xml #输出功能包的package.xml内容

 创建节点模板(python):

#导包
import rclpy
from rclpy.node import Node

#自定义类
class Mynode(Node):
    def __init__(self):
        super().__init__("hello_node_py") #ROS节点名称
        #获取当前节点的日志记录器,并调用info方法输出一条日志信息。
        self.get_logger().info("hello world!(py继承)") 

def main():
    #初始化
    rclpy.init()
    #创建对象
    node = Mynode()
    #...
    #资源释放
    rclpy.shutdown()
    pass

if __name__ == '__main__':
    main()

二、通讯模块

我们知道直接在KEIL上编写程序输入单片机就可以实现对小车的控制,那么ROS2是怎么实现对小车控制的?各种传感器数据如何通过ROS传输交互?带着这些疑问学习:

 功能包安装

二进制安装

sudo apt install ros-ROS2版本号-功能包名称

源码安装

git clone 仓库地址

 自实现

自己写代码好吧

通讯机制术语

二:详细知识https://blog.csdn.net/weixin_46697509/article/details/135000917?ops_request_misc=&request_id=&biz_id=102&utm_term=ros%E9%80%9A%E8%B5%B5%E8%99%9A%E5%B7%A6&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-7-135000917.nonecase&spm=1018.2226.3001.4187

msg文件
msg文件是用于定义话题通信中数据载体的接口文件,一个典型的.msg文件示例如下。

int64 num1
int64 num2

srv文件
        srv(service)文件是用于定义服务通信中数据载体的接口文件,一个典型的.srv文件示例如下。

int64 num1
int64 num2
---
int64 sum

文件中声明的数据被---分割为两部分,上半部分用于声明请求数据,下半部分用于声明响应数据。

action文件
action文件使用用于定义动作通信中数据载体的接口文件,一个典型的.action文件示例如下。

int64 num
---
int64 sum
---
float64 progress

文件中声明的数据被---分割为三部分,上半部分用于声明请求数据,中间部分用于声明响应数据,下半部分用于声明连续反馈数据。

.变量类型
        不管是何种接口文件,在文件中每行声明的数据都由字段类型和字段名称组成,可以使用的字段类型有:

int8, int16, int32, int64 (或者无符号类型: uint*)

float32, float64

string

time, duration

其他msg文件

变长数组和定长数组

ROS中还有一种特殊类型:Header,标头包含时间戳和ROS2中常用的坐标帧信息。许多接口文件的第一行包含Header标头。

另外,需要说明的是:

参数通信的数据无需定义接口文件,参数通信时数据会被封装为参数对象,参数客户端和服务端操作的都是参数对象。

话题通信

发送文本消息(Python)

在src下再建一个py01_topic文件夹 加入std_msgs 等文件的依赖 ,再创建demo01_talker_str_py的py文件

ros2 pkg create py01_topic --build-type ament_python --dependencies rclpy std_msgs --node-name demo01_taker_str_py

package.xml中会有: 

<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>base_interfaces_demo</depend>

setup.py里有:

entry_points={
    'console_scripts': [
        'demo01_talker_str_py = py01_topic.demo01_talker_str_py:main',
    ],
}, 

"""  
    需求:以某个固定频率发送文本“hello world!”,文本后缀编号,每发送一条消息,编号递增1。
    步骤:
        1.导包;
        2.初始化 ROS2 客户端;
        3.定义节点类;
            3-1.创建发布方;
            3-2.创建定时器;
            3-3.组织消息并发布。
        4.调用spin函数,并传入节点对象;
        5.释放资源。
"""
# 1.导包;
import rclpy
from rclpy.node import Node
#导入字符串类型
from std_msgs.msg import String


# 3.定义节点类;
class MinimalPublisher(Node):

    def __init__(self):
        super().__init__('minimal_publisher_py')
        # 3-1.创建发布方;
        self.publisher_ = self.create_publisher(String, 'topic', 10)
        # 3-2.创建定时器;
        timer_period = 0.5 
        self.timer = self.create_timer(timer_period, self.timer_callback)
        self.i = 0

    # 3-3.组织消息并发布。
    def timer_callback(self):
        msg = String()
        msg.data = 'Hello World(py): %d' % self.i
        self.publisher_.publish(msg)
        self.get_logger().info('发布的消息: "%s"' % msg.data)
        self.i += 1


def main(args=None):
    # 2.初始化 ROS2 客户端;
    rclpy.init(args=args)
    # 4.调用spin函数,并传入节点对象;
    minimal_publisher = MinimalPublisher()
    rclpy.spin(minimal_publisher)
    # 5.释放资源。
    rclpy.shutdown()


if __name__ == '__main__':
    main()
  • super().__init__('minimal_publisher_py'):调用父类的初始化方法,设置节点名称为"minimal_publisher_py"

  • self.create_publisher() 方法用于创建一个发布者(Publisher),它允许节点将消息发送到指定的主题。这个方法返回一个 Publisher 对象,你可以通过这个对象来发布消息

  • self.create_timer() 方法用于创建一个定时器(Timer),它会在指定的时间间隔后触发一个回调函数。这个方法返回一个 Timer 对象,你可以通过这个对象来管理定时器的行为

  • rclpy.spin(node) 的主要作用是进入一个无限循环,等待并处理 ROS2 的事件。 


订阅消息

放在一个topic下就可以收到消息啦:发送和订阅都在“chatter”下 

# 1.导包;
import rclpy
from rclpy.node import Node
from std_msgs.msg import String


# 3.定义节点类;
class Listener(Node):

    def __init__(self):
        super().__init__('listener_node_py')
        self.get_logger().info('订阅方创建了py')
        # 3-1.创建订阅方;
        """
        参数:
            消息类型
            话题名称
            回调函数
            Qos队列长度
        """
        self.subscription = self.create_subscription(String,"chatter",self.do_callback,10)

        self.subscription  # 防止未使用的警告

    def do_callback(self,msg):       
        # 3-2.处理订阅到的消息。
        self.get_logger().info("订阅的数据:%s" % msg.data)
        
 


def main():

    rclpy.init()

    rclpy.spin(Listener())

    rclpy.shutdown()



if __name__ == 'main':
    main()

收数据直接调用msg.data打印即可,简单的订阅一般用不上订阅对象


自定义接口

创建话题通信自定义接口消息

0.创建一个c++文件夹(不用依赖),用于存放接口文件信息

 在src下创建base_interfaces_demo文件夹

ros2 pkg create  --build-type ament_cmake base_interfaces_demo

在工作文件夹下加入依赖

 package.xml中会有: 

<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>base_interfaces_demo</depend>

1.功能包base_interfaces_demo文件夹下新建 msg 文件夹,msg文件夹下新建Student.msg文件(首字母大写),文件中输入如下内容:

string   name
int32    age
float64  height

2.编辑配置文件

1.package.xml文件

在package.xml中需要添加一些依赖包,具体内容如下:

  <!-- 编译依赖-->
  <build_depend>rosidl_default_generators</build_depend>
  <!-- 执行依赖-->
  <exec_depend>rosidl_default_runtime</exec_depend>
  <!-- 所属功能包-->
 <member_of_group>rosidl_interface_packages</member_of_group>

2.CMakeLists.txt文件

为了将.msg文件转换成对应的C++和Python代码,还需要在CMakeLists.txt中添加如下配置:


find_package(rosidl_default_generators REQUIRED)
#为接口文件生成源代码
rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/Student.msg"
)

 "msg/Student.msg"是自己建的包和文件,这里根据自己文件名更改

3.编译

终端中进入当前工作空间,编译功能包:

colcon build --packages-select base_interfaces_demo

如果报错,问一下deepseek,之前遇到了conda与python冲突问题,解决:在VScode里输入命令

# 临时禁用 Conda 环境
conda deactivate

# 确认使用的是系统 Python
which python3  # 应显示 /usr/bin/python3

# 重新编译
rm -rf build/ install/ log/
colcon build --packages-select base_interfaces_demo

4.测试

编译完成之后,在工作空间下的install目录下将生成Student.msg文件对应的C++和Python文件,我们也可以在终端下进入工作空间,通过如下命令查看文件定义以及编译是否正常:

. install/setup.bash
ros2 interface show base_interfaces_demo/msg/Student

 Import自定义接口  
from base_interfaces_demo.msg import Student

如果没有Student()提示的话, 配置VSCode中settings.json文件,在文件中的python.autoComplete.extraPaths和python.analysis.extraPaths属性下添加j绝对路径

ctrl+shift+P打开,输入settings.josn 

绝对路径是 在install/local下打开的那个base_interfaces_demo,获取:

右键在集成终端打开

输入pwd,复制到dist-packages就可以

    "python.autoComplete.extraPaths":[
        "/home/jo/ros2_pluming/install/base_interfaces_demo/local/lib/python3.10/dist-packages"
    ],
    "python.analysis.extraPaths":[
        "/home/jo/ros2_pluming/install/base_interfaces_demo/local/lib/python3.10/dist-packages"
    ]


服务通信

 服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即:一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A。

服务通信接口消息

1.创建并编辑 .srv 文件

功能包base_interfaces_demo下新建srv文件夹,srv文件夹下新建AddInts.srv文件,文件中输入如下内容:

int32 num1
int32 num2
---
int32 sum

2.编辑配置文件

1.package.xml 文件

srv文件与msg文件的包依赖一致,如果你是新建的功能包添加srv文件,那么直接参考定义msg文件时package.xml 配置即可。由于我们使用的是base_interfaces_demo该包已经为msg文件配置过了依赖包,所以package.xml不需要做修改。

2.CMakeLists.txt 文件

如果是新建的功能包,与之前定义msg文件同理,为了将.srv文件转换成对应的C++和Python代码,还需要在CMakeLists.txt中添加如下配置:

find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/AddInts.srv"
)

不过,我们当前使用的base_interfaces_demo包,那么你只需要修改rosidl_generate_interfaces函数即可,修改后的内容如下:

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/Student.msg"
  "srv/AddInts.srv"
)

3.编译

终端中进入当前工作空间,编译功能包:

colcon build --packages-select base_interfaces_demo

4.测试

编译完成之后,在工作空间下的 install 目录下将生成AddInts.srv文件对应的C++和Python文件,我们也可以在终端下进入工作空间,通过如下命令查看文件定义以及编译是否正常:

. install/setup.bash
ros2 interface show base_interfaces_demo/srv/AddInts

正常情况下,终端将会输出与AddInts.srv文件一致的内容。

 1.服务端实现

"""  
    需求:编写服务端,接收客户端发送请求,提取其中两个整型数据,相加后将结果响应回客户端。
    步骤:
        1.导包;
        2.初始化 ROS2 客户端;
        3.定义节点类;
            3-1.创建服务端;
            3-2.处理请求数据并响应结果。
        4.调用spin函数,并传入节点对象;
        5.释放资源。

"""

# 1.导包;
import rclpy
from rclpy.node import Node
from base_interfaces_demo.srv import AddInts

# 3.定义节点类;
class Service(Node):

    def __init__(self):
        super().__init__('minimal_service_py')
        # 3-1.创建服务端;
        self.srv = self.create_service(AddInts, 'add_ints', self.add_two_ints_callback)
        self.get_logger().info("服务端启动!")

    # 3-2.处理请求数据并响应结果。
    def add_two_ints_callback(self, request, response):
        response.sum = request.num1 + request.num2
        self.get_logger().info('请求数据:(%d,%d),响应结果:%d' % (request.num1, request.num2, response.sum))
        return response


def main():
    # 2.初始化 ROS2 客户端;
    rclpy.init()
    # 4.调用spin函数,并传入节点对象;
    rclpy.spin(Service())
    # 5.释放资源。
    rclpy.shutdown()


if __name__ == '__main__':
    main()
# AddInts.srv
int64 num1  # 第一个数字
int64 num2  # 第二个数字
---
int64 sum   # 返回的和

其中---作用就是划分Request与Response, 自动生成python类,可以直接.sum或者.num

class AddInts_Request:  # 请求类
    def __init__(self):
        self.num1 = 0  # int64
        self.num2 = 0  # int64

class AddInts_Response:  # 响应类
    def __init__(self):
        self.sum = 0  # int64

注意! 

self.create_service(AddInts, 'add_ints', self.add_two_ints_callback)中 add_two_ints_callback回调函数必须严格接受 2 个参数(request 和 response),不能多、不能少,也不能改变顺序。这是由 ROS2 底层机制强制规定的。命名可以自定义,但是其含义是内定的。 

ROS2 服务端回调函数的返回值必须是 response 对象,这是 ROS2 的强制要求,不能返回其他类型或 None 

客户端解析

"""  
    需求:编写客户端,发送两个整型变量作为请求数据,并处理响应结果。
    步骤:
        1.导包;
        2.初始化 ROS2 客户端;
        3.定义节点类;
            3-1.创建客户端;
            3-2.等待服务连接;
            3-3.组织请求数据并发送;
        4.创建对象调用其功能,处理响应结果;
        5.释放资源。

"""
# 1.导包;
import sys
import rclpy
from rclpy.logging import get_logger
from rclpy.node import Node
from base_interfaces_demo.srv import AddInts

# 3.定义节点类;
class Client(Node):

    def __init__(self):
        super().__init__('minimal_client_py')
        # 3-1.创建客户端;
        self.cli = self.create_client(AddInts, 'add_ints')
        # 3-2.等待服务连接;
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('服务连接中,请稍候...')
        self.req = AddInts.Request()

    # 3-3.组织请求数据并发送;
    def send_request(self):
        self.req.num1 = int(sys.argv[1])
        self.req.num2 = int(sys.argv[2])
        self.future = self.cli.call_async(self.req)


def main():
    #校验工作
    if len(sys.argv)!= 3:
        get_logger("rclpy").info("请提交2个整形参数")
    # 2.初始化 ROS2 客户端;
    rclpy.init()

    # 4.创建对象并调用其功能;
    minimal_client = Client()
    minimal_client.send_request()

    # 处理响应
    rclpy.spin_until_future_complete(minimal_client,minimal_client.future)
    try:
        response = minimal_client.future.result()
    except Exception as e:
        minimal_client.get_logger().info(
            '服务请求失败: %r' % (e,))
    else:
        minimal_client.get_logger().info(
            '响应结果: %d + %d = %d' %
            (minimal_client.req.num1, minimal_client.req.num2, response.sum))

    # 5.释放资源。
    rclpy.shutdown()

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐