最近在负责项目中pyqt界面的制作,但网上关于ros2与pyqt5的中文资料很少,基本都是ros1 ,qt的博客和文章,通过不断的碰壁+摸索,终于实现了以下功能:
1. qt界面中启动节点
2. qt界面通过获取pid进程名关闭节点
3. qt界面通过定时器输出文本
重新写了一个简单的例子,代码如下:
launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
action_1 = Node(
package='village_li',
executable='li4_node',
name='li4_node',
)
return LaunchDescription([
action_1
])
li4.py:
import rclpy #ros2 接口库
from rclpy.node import Node #ros2 节点类
from std_msgs.msg import String #字符串消息类型
class PublisherNode(Node):
def __init__(self, name):
super().__init__(name) # 父类初始化
self.pub = self.create_publisher(String, "cha", 10) #创建发布者对象(消息类型,话题名(密码),长度)
self.i = 0
self.timer = self.create_timer(0.5, self.timer_callback)
def timer_callback(self):
msg = String() #创建消息对象
msg.data = '%d' %(self.i) #实例化消息对象
self.pub.publish(msg) #发布信息
self.get_logger().info('Publishing: "%s"' % msg.data) #输出日志
self.i +=1
def main(args=None):
rclpy.init(args=args) # 初始化python接口函数
node = PublisherNode("li4") #创建发布节点
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
wang2.py:
from lib2to3.pytree import generate_matches
from numpy import tile
import rclpy
import sys
import time
from rclpy.node import Node
from std_msgs.msg import String
from python_qt_binding import loadUi
import numpy as np
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import threading
from PyQt5.QtWidgets import QApplication,QWidget,QTextEdit,QVBoxLayout,QPushButton
import sys
'''
QtGui:多种基本图形功能的类,窗口,事件处理
QtWidgets:包含了一整套ui元素组件,用于建立符合系统分割的classic界面
QtCore:包含核心的非gui功能,涉及到time,文件,目录,数据类型,文本,连接,mime,线程或者进程等对象
'''
#需要杀死的进程
kill_name = ["li4_node"]
class SubscriberNode(Node): #订阅者节点创建
def __init__(self, name):
super().__init__(name)
self.sub=self.create_subscription(String, "cha", self.listener_callback, 20)
self.data = 'None'
self.data_len = 0
def listener_callback(self, msg):
self.data = msg.data
self.data_len = len(self.data)
class TextEditDemo(QWidget):
def __init__(self,parent=None):
super(TextEditDemo, self).__init__(parent)
self.setWindowTitle('QTextEdit 例子')
#定义窗口的初始大小
self.resize(300,270)
#创建多行文本框
self.textEdit=QTextEdit()
self.textEdit.ensureCursorVisible() # 确保光标是在必要时滚动文本编辑可见
self.textEdit.setLineWrapColumnOrWidth(800)# 指定行宽
self.textEdit.setLineWrapMode(QTextEdit.FixedPixelWidth) #设置QTextEdit按行显示,一条信息只显示一行
self.textEdit.setFixedWidth(400) # 文本输出界面宽
self.textEdit.setFixedHeight(200) # 文本输出界面高
self.textEdit.move(30, 50)
#创建三个按钮
self.btnPress0=QPushButton('打开节点')
self.btnPress1=QPushButton('显示文本')
self.btnPress2=QPushButton('关闭文本')
self.btnPress3=QPushButton('关闭节点')
#实例化垂直布局
layout=QVBoxLayout()
#相关控件添加到垂直布局中
layout.addWidget(self.textEdit)
layout.addWidget(self.btnPress0) # 启动发布者节点
layout.addWidget(self.btnPress1) # 打印文本
layout.addWidget(self.btnPress2) # 结束打印(但不会关掉节点)
layout.addWidget(self.btnPress3) # 关掉节点
#设置布局
self.setLayout(layout)
self.timer = QTimer(self) # 创建timer实例对象
#将按钮的点击信号与相关的槽函数进行绑定,点击即触发
self.btnPress0.clicked.connect(self.btnPress0_clicked)
self.btnPress1.clicked.connect(self.btnPress1_clicked)
self.btnPress2.clicked.connect(self.btnPress2_clicked)
self.btnPress3.clicked.connect(self.btnPress3_clicked)
#在界面主线程中开启守护线程启动节点
self.node = None
t_ = threading.Thread(target=self.thread_ros, args=())
t_.setDaemon(True) # 主线程崩掉,守护线程可以自己shutdown
t_.start()
def thread_ros(self):
rclpy.init(args=None) # 初始化python接口函数
self.node = SubscriberNode("wang2")
rclpy.spin(self.node)
self.node.destroy_node()
rclpy.shutdown()
def btnPress0_clicked(self):
ROS_PROGRAM = QProcess(self)
self.textEdit.setPlainText("launching")
program = 'ros2 launch ./src/village_li/village_li/launch.py'
ROS_PROGRAM.start(program)
def btnPress1_clicked(self):
#以文本的形式输出到多行文本框
self.setup_ui() # 打开定时器
self.textEdit.setPlainText("开始打印:")
def btnPress2_clicked(self):
#以文本的形式输出到多行文本框
self.timer.stop() # 关闭定时器
self.textEdit.setPlainText("已经清零了!")
def btnPress3_clicked(self): # 关闭节点
global kill_name
self.kill_pid(kill_name)
def kill_pid(self,name):
'''
作用:根据进程名获取进程pid,然后杀死进程
'''
pids = psutil.process_iter()
for pid in pids:
for i in range(0,len(name)):
if(pid.name() == name[i]):
os.kill((pid.pid), signal.SIGTERM)
def setup_ui(self):
self.timer = QTimer() # 点击一次就初始化一次
self.timer.start(500) # 设置计时间隔并启动;单位毫秒
self.timer.timeout.connect(self.operate) # 每次计时到时间时发出信号
def operate(self):
self.textEdit.append(self.node.data + '\t' + str(self.node.data_len))
self.textEdit.moveCursor(QTextCursor.End)# 确保光标是在必要时滚动文本编辑可见
def main(args=None):
app=QApplication(sys.argv)
win=TextEditDemo()
win.show()
sys.exit(app.exec_())
setup.py
from setuptools import setup
package_name = 'village_li'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='wxy',
maintainer_email='wxy@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
"li4_node = village_li.li4:main", #village_li:代表文件夹的名字 li4:代表文件的名字
"wang2_node = village_li.wang2:main",
],
},
)