MRobot/MRobot.py
2025-06-17 18:00:13 +08:00

246 lines
8.7 KiB
Python

import sys
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication
import serial
import serial.tools.list_ports
from PyQt5.QtGui import QTextCursor
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QHBoxLayout, QComboBox, QPushButton, QTextEdit, QLineEdit, QLabel
from PyQt5.QtWidgets import QGroupBox, QGridLayout, QSizePolicy
from qfluentwidgets import (
NavigationItemPosition, Theme, FluentWindow, NavigationAvatarWidget,
PushButton, FluentIcon
)
from qfluentwidgets import FluentIcon as FIF
# ===================== 页面基类 =====================
class BaseInterface(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
# ===================== 首页界面 =====================
class HomeInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("homeInterface")
layout = QVBoxLayout()
self.setLayout(layout)
# ===================== 代码生成页面 =====================
class DataInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("dataInterface")
layout = QVBoxLayout()
self.setLayout(layout)
# ===================== 串口终端界面 =====================
class SerialReadThread(QThread):
data_received = pyqtSignal(str)
def __init__(self, ser):
super().__init__()
self.ser = ser
self._running = True
def run(self):
while self._running:
if self.ser and self.ser.is_open and self.ser.in_waiting:
try:
data = self.ser.readline().decode(errors='ignore')
self.data_received.emit(data)
except Exception:
pass
def stop(self):
self._running = False
self.wait()
class SerialTerminalInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("serialTerminalInterface")
layout = QVBoxLayout(self)
# 串口选择和连接
hbox = QHBoxLayout()
self.port_combo = QComboBox()
self.refresh_ports()
self.baud_combo = QComboBox()
self.baud_combo.addItems(['9600', '115200', '57600', '38400', '19200', '4800'])
self.connect_btn = QPushButton("连接")
self.connect_btn.clicked.connect(self.toggle_connection)
hbox.addWidget(QLabel("串口:"))
hbox.addWidget(self.port_combo)
hbox.addWidget(QLabel("波特率:"))
hbox.addWidget(self.baud_combo)
hbox.addWidget(self.connect_btn)
layout.addLayout(hbox)
# 预设命令区
preset_group = QGroupBox("预设命令")
preset_layout = QGridLayout()
self.preset_commands = [
("复位", "RESET"),
("获取版本", "GET_VERSION"),
("启动", "START"),
("停止", "STOP"),
("自检", "SELF_TEST"),
("查询状态", "STATUS?"),
]
for i, (label, cmd) in enumerate(self.preset_commands):
btn = QPushButton(label)
btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
btn.clicked.connect(lambda _, c=cmd: self.send_preset_command(c))
preset_layout.addWidget(btn, i // 3, i % 3)
preset_group.setLayout(preset_layout)
layout.addWidget(preset_group)
# 显示区
self.text_edit = QTextEdit()
self.text_edit.setReadOnly(True)
self.text_edit.setStyleSheet(
"background-color: #1e1e1e; color: #d4d4d4; font-family: Consolas, 'Courier New', monospace; font-size: 14px;"
)
layout.addWidget(self.text_edit)
# 输入区
input_hbox = QHBoxLayout()
self.input_line = QLineEdit()
self.input_line.setPlaceholderText("输入内容,回车发送")
self.input_line.returnPressed.connect(self.send_data)
send_btn = QPushButton("发送")
send_btn.clicked.connect(self.send_data)
input_hbox.addWidget(self.input_line)
input_hbox.addWidget(send_btn)
layout.addLayout(input_hbox)
self.ser = None
self.read_thread = None
def send_preset_command(self, cmd):
self.input_line.setText(cmd)
self.send_data()
def refresh_ports(self):
self.port_combo.clear()
ports = serial.tools.list_ports.comports()
for port in ports:
self.port_combo.addItem(port.device)
def toggle_connection(self):
if self.ser and self.ser.is_open:
self.disconnect_serial()
else:
self.connect_serial()
def connect_serial(self):
port = self.port_combo.currentText()
baud = int(self.baud_combo.currentText())
try:
self.ser = serial.Serial(port, baud, timeout=0.1)
self.connect_btn.setText("断开")
self.text_edit.append(f"已连接到 {port} @ {baud}")
self.read_thread = SerialReadThread(self.ser)
self.read_thread.data_received.connect(self.display_data)
self.read_thread.start()
except Exception as e:
self.text_edit.append(f"连接失败: {e}")
def disconnect_serial(self):
if self.read_thread:
self.read_thread.stop()
self.read_thread = None
if self.ser:
self.ser.close()
self.ser = None
self.connect_btn.setText("连接")
self.text_edit.append("已断开连接")
def display_data(self, data):
self.text_edit.moveCursor(QTextCursor.End)
self.text_edit.insertPlainText(data)
self.text_edit.moveCursor(QTextCursor.End)
def send_data(self):
if self.ser and self.ser.is_open:
text = self.input_line.text()
try:
if not text:
# 内容为空,只发送换行
self.ser.write('\n'.encode())
else:
# 逐字符发送
for char in text:
self.ser.write(char.encode())
# 结尾加换行
self.ser.write('\n'.encode())
except Exception as e:
self.text_edit.append(f"发送失败: {e}")
self.input_line.clear()
# ===================== 设置界面 =====================
class SettingInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("settingInterface")
layout = QVBoxLayout()
self.setLayout(layout)
# ===================== 帮助与关于界面 =====================
class HelpInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("helpInterface")
layout = QVBoxLayout()
self.setLayout(layout)
class AboutInterface(BaseInterface):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("aboutInterface")
layout = QVBoxLayout()
self.setLayout(layout)
# ===================== 主窗口与导航 =====================
class MainWindow(FluentWindow):
themeChanged = pyqtSignal(Theme)
def __init__(self):
super().__init__()
self.setWindowTitle("MR_ToolBox")
self.resize(1000, 700)
self.setMinimumSize(800, 600)
self.page_registry = [
(HomeInterface(self), FIF.HOME, "首页", NavigationItemPosition.TOP),
(DataInterface(self), FIF.LIBRARY, "MRobot代码生成", NavigationItemPosition.SCROLL),
(SerialTerminalInterface(self), FIF.COMMAND_PROMPT, "串口终端", NavigationItemPosition.SCROLL),
(SettingInterface(self), FIF.SETTING, "设置", NavigationItemPosition.BOTTOM),
(HelpInterface(self), FIF.HELP, "帮助", NavigationItemPosition.BOTTOM),
(AboutInterface(self), FIF.INFO, "关于", NavigationItemPosition.BOTTOM),
]
self.initNavigation()
def initNavigation(self):
for page, icon, name, position in self.page_registry:
self.addSubInterface(page, icon, name, position)
self.navigationInterface.addSeparator()
avatar = NavigationAvatarWidget('用户', ':/qfluentwidgets/images/avatar.png')
self.navigationInterface.addWidget(
routeKey='avatar',
widget=avatar,
onClick=None,
position=NavigationItemPosition.BOTTOM
)
# ===================== 程序入口 =====================
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()