Node间进行通讯,其中发送消息的一方,ROS将其定义为 Publisher(发布者) ,将接收消息的一方定义为 Subscriber(订阅者) 。
考虑到消息需要广泛传播,ROS没有将其设计为点对点的单一传递,而是由 Publisher 将信息发布到 Topic(主题) 中,想要获得消息的任何一方都可以到这个 Topic 中去取数据。
publisher
subscriber
topic
主题工作空间下创建功能包
cd ros_ws/first_ws/src
catkin_create_pkg hello_topic rospy roscpp rosmsg
使用Clion打开目录 hello_topic
,在 scripts
目录下新建Python文件 publisher_node
创建节点
rospy.init_node('publisher_node')
创建发布者
publisher = rospy.Publisher(topic_Name, String, queue_size=100)
第一个参数为
topic
名
第二个参数为发布的消息类
第三个参数为topic
中消息队列最多的数量。
此时应在开头添加
from std_msgs.msg import String
定时发布消息
rate = rospy.Rate(4)
count = 0
while not rospy.is_shutdown():
# 往外发送数据
msg = String()
msg.data = 'hello topic {}'.format(count)
publisher.publish(msg)
rate.sleep()
count += 1
完整代码如下
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 6/9/20 7:06 AM
# @Author : Chenan_Wang
# @File : publisher_node.py
# @Software: CLion
import rospy
from std_msgs.msg import String
if __name__ == '__main__':
# 创建节点
rospy.init_node('publisher_node')
# topic name 主题名称,唯一标示作用(类似于广播频段)
# 命名规则 ‘/a/b/c/d'
topic_name = '/hello/publisher'
# date_class:数据类型 目前暂时使用 字符串
publisher = rospy.Publisher(topic_name, String, queue_size=100)
count = 0
rate = rospy.Rate(4)
while not rospy.is_shutdown():
# 往外发送数据
msg = String()
msg.data = 'hello topic {}'.format(count)
publisher.publish(msg)
rate.sleep()
count += 1
查看所有的主题
rostopic list
打印主题所发布的信息
rostopic echo /hello/publisher
通过命令启动rqt_topic工具
rosrun rqt_topic rqt_topic
选中要调试的主题
在 scripts
目录下新建Python文件 publisher_node
创建节点
rospy.init_node('subscriber_node')
创建订阅者
rospy.Subscriber(topic_name, String, topic_callback)
rospy.spin()
实现订阅回调
def topic_callback(msg):
print msg
完整代码如下
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 6/9/20 7:55 AM
# @Author : Chenan_Wang
# @File : subscriber_node.py
# @Software: CLion
import rospy
from std_msgs.msg import String
def topic_callback(msg):
print msg
if __name__ == '__main__':
# 创建节点
rospy.init_node('subscriber_node')
# 订阅topic消息,subscriber
topic_name = '/hello/itcast'
# 创建订阅者
subscriber = rospy.Subscriber(topic_name, String, topic_callback)
# callback是异步回调:会在另一个线程进行调用,所以需要阻塞:spin
rospy.spin()
rosrun hello_topic publisher_node.py
查询主题所需要的数据类型
rostopic type /hello/publisher
模拟发布数据
rostopic pub /hello/publisher std_msgs/String hello -r 10
rostopic pub
是模拟发布数据的命令
/hello/publisher
是将数据发送到那个主题,根据自己实际调试的主题来写
std_msgs/String
是这个主题所需要的数据类型,通过rostopic type /hello/publisher
查询出来
hello
是发送的数据,根据自己的调试需求来写
-r
指的是发送频率
通过命令启动rqt_publisher工具
rosrun rqt_publisher rqt_publisher
查询所有的topic
rostopic list
查询topic详情
rostopic info
查询topic传输数据类型
rostopic type topic名称
查询topic传输的频率
rostopic hz topic名称
查询topic传输数据的带宽
rostopic bw topic名称
调试publisher数据,模拟数据接收
rostopic echo topic名称
调试subscriber,模拟数据发送
rostopic pub topic名称 topic数据类型 topic数据 参数
启动小乌龟模拟器节点
rosrun turtlesim turtlesim_node
启动小乌龟键盘输入节点
rosrun turtlesim turtle_teleop_key
启动完成后,可以通过键盘输入操控小乌龟移动
键盘操控时,光标一定要在命令行上
通过命令可以查看 /turtlesim
节点的详情
rosnode info /turtlesim
rosnode info
命令可以查看当前节点的一些信息
Publications
:此节点上定义的发布者Subscriptions
:此节点上定义的订阅者Services
:此节点上定义的服务Pid
:占用的网络端口Connections
: 此节点和其他节点间的连接信息
查看控制节点
同理,我们也可以通过rosnode info查询/teleop_turtle节点的信息,
rosnode info /teleop_turtle
rqt_graph
工具提供了可视化的工具方便我们查看这种节点间的关系:
rosrun rqt_graph rqt_graph
图像显示,
/teleop_turtle
通过主题/turtle1/cmd_vel
给turtlesim
进行数据传
/teleop_turtle
为具备Publisher
功能的节点
/turtlesim
为具备Subscriber
功能的节点
/turtle1/cmd_vel
为publisher
和subscriber
通讯的主题
启动rqt_publisher工具
rosrun rqt_publisher rqt_publisher
rostopic pub /turtle1/cmd_vel geometry_msgs/Twist "linear:
x: 1.0
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 3.0"
完整代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 7/9/20 12:59 AM
# @Author : Chenan_Wang
# @File : turtle_ctrl.py
# @Software: CLion
from PyQt5.QtWidgets import *
import sys
import rospy
from window import MainWindow, TurtleWindow
if __name__ == '__main__':
rospy.init_node('turtle_ctrl_node')
# Qt ui部分
app = QApplication(sys.argv)
# 窗体展示
Windows = TurtleWindow()
Windows.show()
sys.exit(app.exec_())
完整代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 7/9/20 1:04 AM
# @Author : Chenan_Wang
# @File : window.py
# @Software: CLion
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
import rospy
from geometry_msgs.msg import Twist # geometry_msg/Twist
from turtlesim.msg import Pose # turtlesim/Pose
from math import degrees, radians
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle('小乌龟控制')
self.resize(400, 0)
# 布局
layout = QFormLayout()
self.setLayout(layout)
# 输入框
self.le_linear = QLineEdit()
self.le_angular = QLineEdit()
# 按钮
btn = QPushButton('发送')
layout.addRow('线速度', self.le_linear)
layout.addRow('线速度', self.le_angular)
layout.addRow(btn)
# 事件
btn.clicked.connect(self.click_send)
topic_name = '/turtle1/cmd_vel'
# geometry_msgs/Twist
self.publisher = rospy.Publisher(topic_name, Twist, queue_size=100)
def click_send(self):
linear = float(self.le_linear.text())
angular = float(self.le_angular.text())
# 通过publisher发送topic消息
twist = Twist()
# 线速度
twist.linear.x = linear
# 角速度
twist.angular.z = angular
self.publisher.publish(twist)
class TurtleWindow(QWidget):
def __init__(self):
super(TurtleWindow, self).__init__()
# 创建自己的渲染(刷新的定时器)
update_timer = QTimer(self)
# 设置定时器的频率
update_timer.setInterval(20)
update_timer.start()
# 监听 timer 事件
update_timer.timeout.connect(self.on_update)
self.setWindowTitle('小乌龟控制')
self.resize(400, 0)
# 布局
layout = QFormLayout()
self.setLayout(layout)
# 输入框
self.le_linear = QLineEdit()
self.le_angular = QLineEdit()
# 文本显示
self.lb_x = QLabel()
self.lb_y = QLabel()
self.lb_linear = QLabel()
self.lb_angular = QLabel()
self.lb_theta = QLabel()
# 按钮
btn = QPushButton('发送')
layout.addRow('线速度', self.le_linear)
layout.addRow('角速度', self.le_angular)
layout.addRow('X坐标', self.lb_x)
layout.addRow('Y坐标', self.lb_y)
layout.addRow('当前线速度', self.lb_linear)
layout.addRow('当前角速度', self.lb_angular)
layout.addRow('当前角度值', self.lb_theta)
layout.addRow(btn)
# 事件
btn.clicked.connect(self.click_send)
topic_name = '/turtle1/cmd_vel'
# geometry_msgs/Twist
self.publisher = rospy.Publisher(topic_name, Twist, queue_size=100)
# 去订阅小乌龟的位置相关信息
pose_topic_name = '/turtle1/pose'
rospy.Subscriber(pose_topic_name, Pose, self.pose_callback)
def click_send(self):
linear = float(self.le_linear.text())
angular = float(self.le_angular.text())
# 角度转弧度
angular = radians(angular)
# 通过publisher发送topic消息
twist = Twist()
# 线速度
twist.linear.x = linear
# 角速度
twist.angular.z = angular
self.publisher.publish(twist)
def pose_callback(self, msg):
if not isinstance(msg, Pose): return
# 赋值操作
self.lb_x.setText(str(msg.x))
self.lb_y.setText(str(msg.y))
self.lb_linear.setText(str(msg.linear_velocity))
self.lb_angular.setText(str(msg.angular_velocity))
self.lb_theta.setText(str(degrees(msg.theta)))
def on_update(self):
# 手动渲染ui
self.update()
if rospy.is_shutdown():
# 关闭
# 需要关闭ui窗口
self.close()
完整代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 12/9/20 7:46 PM
# @Author : Chenan_Wang
# @File : driver_node.py
# @Software: CLion
import rospy
import serial # 串口
import struct
if __name__ == '__main__':
# 创建节点
rospy.init_node('my_driver_node')
ser = serial.Serial(port='dev/ttyUSB0', baudrate=115200)
# 电机驱动
pack = struct.pack('h', 5000)
data = bytearray([0x03, pack[0], pack[1]])
ser.write(data)
rospy.spin()
完整代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 12/9/20 7:46 PM
# @Author : Chenan_Wang
# @File : driver_node.py
# @Software: CLion
import rospy
import serial # 串口
import struct
from std_msgs.msg import Int32
def motor_callback(msg):
if not isinstance(msg, Int32): return
# 接收到其他节点发送的数据
pwm = msg.data
# 给下位机发送指令
# 电机驱动
pack = struct.pack('h', pwm)
data = bytearray([0x03, pack[0], pack[1]])
ser.write(data)
if __name__ == '__main__':
# 创建节点
rospy.init_node('my_driver_node')
# 串口创建
# 重试机制
count = 0
while count < 10:
count += 1
try:
ser = serial.Serial(port='/dev/ttyUSB0', baudrate=115200)
# 如果出错了,后面的代码就不执行了
# 能到达这个位置说明,链接成功
break
except Exception as e:
print(e)
# 创建一个电机指令的订阅者
motor_topic_name = '/motor'
rospy.Subscriber(motor_topic_name, Int32, motor_callback)
rospy.spin()
完整代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 12/9/20 7:46 PM
# @Author : Chenan_Wang
# @File : driver_node.py
# @Software: CLion
import rospy
import serial # 串口
import struct
from std_msgs.msg import Int32, Float32
def motor_callback(msg):
if not isinstance(msg, Int32): return
# 接收到其他节点发送的数据
pwm = msg.data
# 给下位机发送指令
# 电机驱动
pack = struct.pack('h', pwm)
data = bytearray([0x03, pack[0], pack[1]])
ser.write(data)
if __name__ == '__main__':
# 创建节点
rospy.init_node('my_driver_node')
# 串口创建
# 重试机制
count = 0
while count < 10:
count += 1
try:
ser = serial.Serial(port='/dev/ttyUSB0', baudrate=115200)
# 如果出错了,后面的代码就不执行了
# 能到达这个位置说明,链接成功
break
except Exception as e:
print(e)
# 创建一个电机指令的订阅者
motor_topic_name = '/motor'
rospy.Subscriber(motor_topic_name, Int32, motor_callback)
# 编码器
encorder_topic_name = '/rpm'
rpm_publisher = rospy.Publisher(encorder_topic_name, Float32, queue_size=100)
# 和下位机进行通讯
while not rospy.is_shutdown():
# 阻塞式的函数
read = ser.read(2)
data = bytearray([])
data.extend(read)
# bytearray 数据 -> 数字类型
data = struct.unpack('h', data)[0]
rpm = data / 100.0
# 将数据发布出去
msg = Float32
msg.data = rpm
rpm_publisher.publish(msg)
rospy.spin()