一个节点发布消息,另外一个节点订阅该消息,即一个发布方Talker,订阅方Listener,传输是数据就是话题topic
目标:使用自定义数据类型实现数据交互
关于自定义数据msg
# /msg/
string name
uint32 age
float64 height
# 然后要配置文件/CMakeLists.txt, package.xml,再编译
2.配置CMakeLists.txt文件
# /package.xml
# 添加到对应位置
message_generation
message_runtime
3.配置package.xml文件
# /CMakeList.txt
find_package(catkin REQUIRED COMPONENTS
-snap-
message_generation
)
# 编译需要
add_message_files(
FILES
Person.msg
)
generate_message(
DEPENDENCIES
std_msgs
)
# find_package所依赖
catkin_package(
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
)
1.配置vscode settings.json,将编译生成的中间文件dist-packages目录添加
2.关于Talker部分代码
#! /usr/bin/env python
# scripts/demo03_pub_person_p.py
import rospy
from plumbing_pub_sub.msg import Person
if __name__=="__main__":
rospy.init_node("China")
pub=rospy.Publisher("person",Person,queue_size=10)
p=Person()
p.name="Lihua"
p.age=18
p.height=171.3
rate=rospy.Rate(0.5)
while not rospy.is_shutdown():
pub.publish(p)
rospy.loginfo("the published messages is: {0}, {1}, {2}".format(p.name, p.age, p.height))
rate.sleep()
# 添加权限、配置文件、编译
3.关于Listener实现
#! scripts/usr/bin/env python
# scripts/demo04_sub_person_p.py
import rospy
from plumbing_pub_sub.msg import Person
def doPerson(p):
rospy.loginfo("the subscribed message is: {}, {}, {}".format(p.name, p.age, p.height))
if __name__=="__main__":
rospy.init_node("Listener1")
sub=rospy.Subscriber("person",Person, doPerson)
rospy.spin()
# 添加权限、配置文件、编译
概念:服务通信基于请求响应模式,是一种应答机制,寄:A节点向B节点发送请求,B节点接收请求并响应结果返回给A
作用:用于偶然的、对时效性要求、对一定逻辑处理需求的数据传输场景
案例:实现两个数字的求和,客户端节点发送两个数字,服务器端点接收数字后求和并返回给客户端
srv文件内可用数据类型与msg文件一致,且定义srv实现流程与自动逸msg实现流程类似:按照固定格式创建srv文件;编辑配置文件;生成
1.创建plumbing_server_client -->
# /srv/-->AddInts
int32 num1
int32 num2
---
int32 num3
# srv中请求和响应的数据用---分割
2.配置CMakeLists.txt、package.xml文件
# /package.xml
# 添加到对应位置
message_generation
message_runtime
# /CMakeList.txt
find_package(catkin REQUIRED COMPONENTS
-snap-
message_generation
)
add_service_files(
FILES
.srv
)
generate_message(
DEPENDENCIES
std_msgs
)
# find_package所依赖
catkin_package(
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
)
# 配置完两个文件按后进行编译生成中间文件
1.配置vscode settings.json,将编译生成的中间文件dist-packages目录添加
2.Server实现
# //-->demo01_server_p.py
#! usr/bin/env python
import rospy
from plumbing_server_client.srv import AddInts,AddIntsRequest,AddIntsResponse
def doNum(request):
# 封装并处理请求的数据
num1 = request.num1
num2 = request.num2
s = num1+num2
response = AddIntsResponse()
response.num3 = s
# 此处响应response的属性名必须是srv定义的变量名
rospy.loginfo("the collected data are: {}, {}".format(num1, num2))
return response
if __name__=="__main__":
rospy.init_node("server")
rospy.loginfo("the server has been started")
# 创建server对象
server=rospy.Service('Sum', AddInts, doNum)
rospy.spin()
# 添加权限,配置文件,编译
# rosservice call sum "num1:10 num2:20"
3.Client实现
# //-->demo02_server_p.py
#! usr/bin/env python
import rospy
from plumbing_server_client.srv import AddInts, AddIntsRequest, AddIntsResponse
if __name__=="__main__":
rospy.init_node("client")
client=rospy.ServiceProxy('Sum', AddInts)
response = client.call(num1=10, num2=20)
rospy.loginfo("the response data is {}".format(response.num3))
# 添加权限,配置文件,编译
3+.Client优化实现——可在执行节点时,动态传入参数
在cmd中调用节点命令时,可附加上输入的参数
#! usr/bin/env python
import rospy
from plumbing_server_client.srv import AddInts, AddIntsRequest, AddIntsResponse
if __name__=="__main__":
if len(sys.argv) != 3:
# argv为数组,需先判断数组的长度;[0]是文件名,[1],[2]对应着num1,num2
rospy.logerr("Incoming data is incorrect")
sys.exit(1)
rospy.init_node("client")
client=rospy.ServiceProxy('Sum', AddInts)
num1=int(sys.argv[1])
num2=int(sys.argv[2])
#client.wait_for_service()
#rospy.wait_for_service('Sum')
# 等待服务器启动
response=client.call(num1, num2)
rospy.loginfo("the response data is {}".format(response.num3))
# 添加权限,配置文件,编译
# rosrun plumbing_server_client demo02_client_py x y
存在问题:若client先于server启动,会抛出异常;若想令client先于server启动时不要抛出异常而是挂起,等待服务器
实现:ros中内置了相关函数,这些函数可以判断服务器的状态,如果服务器没有启动,那么就让客户端挂起
1.使用client客户端对象方法
client.wait_for_service()
2.使用rospy方法
rospy.wait_for_service('topic')
参数服务器主要用于不同节点之间的数据共享,参数服务器相当于时独立于所有节点的一个公共容器,可以将数据存储容器中,可以被不同节点所调用,不同节点也可以往里存储服务器
应用场景:导航实现时,会进行路径规划;全局路径规划,设计一个从出发点到目标点的大致路径;本地路径规划,会根据当前路况实时生成行进的路径
参数服务器,一般用于存在数据共享的一些应用场景
概念:已共享的方式实现不同节点之间数据交互的通信模式
作用:存储一些多节点共享的数据,类似于全局变量
案例:实现参数 增删改查操作
参数可以使用数据类型
32-bit integers #4字节整型数据
booleans #布尔值
strings #文本
doubles #浮点数
iso8601 dates #iso8601时间表示方法
lists #列表
dic #字典
base64-encoded binary data #以base64编码的二进制数据
注意:参数服务器不是为高性能而设计的,因此最好用于存储静态的非二进制的简单数据
1.增&改
rospy.set_param(
# plumbing_param_server/scripts/demo01_param_set_p.py
#! usr/bin/env python
import rospy
if __name__=="__main__":
rospy.init_node('param_set_p')
rospy.set_param('type_p', 'common')
rospy.set_param('radius_p', 0.15)
# 新增两组参数
# 添加权限,配置文件,编译
# rosrun
# rosparam list #列出当前参数-键
# rosparam get #得到对应key的value
2.查询参数
rospy.get_param(
rospy.get_param_cached(
rospy.get_param_names()
rospy.has_param(
# plumbing_param_server/scripts/demo02_param_get_p.py
#! /usr/bin/env python
import rospy
if __name__=="__main__":
rospy.init_node("get_param_p")
r=rospy.get_param('radius_p', 0.5)
# 获取key=radius_p的value,并且设置默认值为0.5
p=rospy.get_param('radius', 0.5)
r1=rospy.get_param_cached('radius_p', 0.5)
# 从缓存里查询数据
keys=rospy.get_param_names()
# 获取键,返回列表?元组?
bool1=rospy.has_param('radius_p')
# 判断key=radius_p是否存在返回bool
rospy.loginfo('radius_p = {}'.format(r))
rospy.loginfo('radius = {}'.format(p))
rospy.loginfo('radius_p1 = {}'.format(r1))
for n in keys:
rospy.loginfo('key = {}'.format(n))
if bool1:
rospy.loginfo('radius_p exist')
else:
rosypy.loginfo('radius_p doesn\'t exist ')
3.删除
rospy.delete_param(
# plumbing_param_server/scriptsdemo03_param_del_p.py
#! /usr/bin/env python
import rospy
if __name__=='__main__':
rospy.init_node('del_param_p')
try:
rospy.delete_param('radius_p')
except Exception as e:
rospy.loginfo('the data doesns\'t exist')
三种通信机制中,参数服务器是一种数据共享机制,可以在不同节点之间共享数据;话题通信和服务通信时在不同节点之间传递数据,三者时ROS中最基础的通信机制
二者的实现流程设计四个要素
topic | service | |
---|---|---|
通信模式 | 发布/订阅 | 请求/响应 |
同步性 | 异步 | 同步 |
底层协议 | ROSTCP/ROSUDP | ROSTCP/ROSUDP |
缓冲区 | 有 | 无 |
时时性 | 弱 | 强 |
节点关系 | 多对多 | 一对多 |
通信数据 | msg | srv |
使用场景 | 连续高频的数据发布和接收 | 偶尔调用或执行的某一项功能 |