lowermachine/source/sim_uppermachine/main.py
lyz 4f6a3953c8 feat(app): 新增了模拟上位机的软件程序
1. 新增了模拟上位机软件,方便调试下位机
2. 编写了模拟上位机的使用说明文档
3. 修改了README的相关部分
2023-06-20 18:54:42 +08:00

329 lines
14 KiB
Python

"""
BY: Miaow, Wanderson M.Pimenta
PROJECT MADE WITH: Qt Designer and PySide6
V: 1.0.0
This project can be used freely for all uses, as long as they maintain the
respective credits only in the Python scripts, any information in the visual
interface (GUI) can be modified without any implication.
There are limitations on Qt licenses if you want to use your products
commercially, I recommend reading them on the official website:
https://doc.qt.io/qtforpython/licenses.html
"""
import re
import sys
import os
import time
from threading import Thread, Event
from binascii import unhexlify, hexlify
from typing import Optional
import json
import PySide6
from modules.list_model import PacketListModel
from modules.protocol import Protocol
from PySide6.QtCore import Signal
from modules import *
from PySide6.QtWidgets import QMainWindow, QApplication, QComboBox, QLineEdit, QSpinBox
from PySide6.QtGui import QIcon, QFontMetrics, QStandardItemModel, QStandardItem
from modules.ui_main import Ui_MainWindow
from modules.server import Server
# os.environ["QT_FONT_DPI"] = "96" # FIX Problem for High DPI and Scale above 100%
class MainWindow(QMainWindow):
connected_signal = Signal()
disconnected_signal = Signal()
send_signal = Signal(bytes)
receive_signal = Signal(bytes)
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.packet_list_model = QStandardItemModel()
with open("settings.json") as f:
self.settings = json.load(f)
UIFunctions.uiDefinitions(self) # set ui, e.g. title bar, grip & resize, min/max/close
self.total_send_bytes, self.total_send_packets = 0, 0
self.total_receive_bytes, self.total_receive_packets = 0, 0
# ===buttons on left menu bar===
self.ui.toggleButton.clicked.connect(lambda: UIFunctions.toggleMenu(self, True))
self.ui.btn_home.clicked.connect(self.menu_btn_clicked)
self.ui.btn_connection.clicked.connect(self.menu_btn_clicked)
self.ui.btn_divider.clicked.connect(self.menu_btn_clicked)
self.ui.btn_valvedata.clicked.connect(self.menu_btn_clicked)
# set custom theme
# UIFunctions.theme(self, "themes\py_dracula_light.qss", True)
# AppFunctions.setThemeHack(self)
# ===page_connection===
page_connection_settings = self.settings["page_connection"]
self.ui.sb_server_port.setValue(page_connection_settings["server_port"])
# ===page_divider===
page_divider_settings = self.settings["page_divider"]
[self.ui.__dict__[f"pb_send_{i}"].clicked.connect(self.pb_send_clicked) for i in range(1, 6)]
for t in ["start", "command", "data", "end"]:
for i in range(1, 6):
cb = self.ui.__dict__[f"cb_{t}_{i}"]
cb.currentTextChanged.connect(self.manual_current_text_changed)
cb.clear()
cb.addItems(page_divider_settings["manual"][f"cb_{t}_list"])
cb.setCurrentText(page_divider_settings["manual"]["data"][i][f"cb_{t}"])
[self.ui.__dict__[f"pb_send_camera_{t}"].clicked.connect(self.pb_send_camera_clicked) for t in "abcd"]
self.ui.le_preset_num.textChanged.connect(self.le_preset_num_text_changed)
self.ui.le_preset_num.setText(str(page_divider_settings["preset"]["le_preset_num"]))
self.ui.btn_preset_next.clicked.connect(self.btn_preset_next_clicked)
self.ui.btn_preset_previous.clicked.connect(self.btn_preset_previous_clicked)
self.ui.btn_preset_add.clicked.connect(self.btn_preset_add_clicked)
self.ui.pb_send_camera_all.clicked.connect(self.pb_send_camera_all_clicked)
self.ui.pb_send_start.clicked.connect(self.pb_send_start_clicked)
self.ui.pb_send_stop.clicked.connect(self.pb_send_stop_clicked)
self.ui.pb_divider_restore.clicked.connect(self.pb_divider_restore_clicked)
# ===show main window, start at page_home page with stopped status===
self.show()
self.ui.btn_home.click()
self.connected_signal.connect(self.slot_connected_signal)
self.disconnected_signal.connect(self.slot_disconnected_signal)
self.send_signal.connect(self.slot_send_signal)
self.receive_signal.connect(self.slot_receive_signal)
self.server: Optional[Server] = None
self.init_server()
def init_server(self):
if self.server is not None:
self.server.close()
self.server = Server()
self.server.set_connected_signal(self.connected_signal)
self.server.set_disconnected_signal(self.disconnected_signal)
self.server.set_send_signal(self.send_signal)
self.server.set_receive_signal(self.receive_signal)
last_selected = self.ui.cb_server_ip.currentText()
self.ui.cb_server_ip.clear()
ip_list = Server.get_server_ip_list()
self.ui.cb_server_ip.addItems(ip_list)
if last_selected is not None and last_selected.strip() != "" and last_selected in ip_list:
self.ui.cb_server_ip.setCurrentText(last_selected)
else:
self.ui.cb_server_ip.setCurrentIndex(0)
def slot_send_signal(self, data: bytes):
self.total_send_bytes += len(data)
self.total_send_packets += 1
self.ui.lbl_tx_count.setText(f"{self.total_send_bytes} bytes | {self.total_send_packets} packets")
data = hexlify(data, " ").decode("ascii").upper()
UIFunctions.set_elide_text(self.ui.lbl_datagram, data)
item = QStandardItem(data)
item.setIcon(QIcon(u":/icons/images/icons/cil-cloud-upload-small.png"))
self.packet_list_model.appendRow(item)
def slot_receive_signal(self, data: bytes):
self.total_receive_bytes += len(data)
self.total_receive_packets += 1
self.ui.lbl_rx_count.setText(f"{self.total_receive_bytes} bytes | {self.total_receive_packets} packets")
if data != b"\xaa\x00\x00\x00\x03hb\xff\xff\xff\xbb":
data = hexlify(data, " ").decode("ascii").upper()
UIFunctions.set_elide_text(self.ui.lbl_datagram, data)
item = QStandardItem(data)
item.setIcon(QIcon(u":/icons/images/icons/cil-cloud-download-small.png"))
self.packet_list_model.appendRow(item)
def slot_connected_signal(self):
self.ui.page_divider.setEnabled(True)
self.ui.lbl_server_addr.setText(f"{self.server.ip}:{self.server.port}")
self.ui.lbl_client_addr.setText(f"{self.server.client_ip}:{self.server.client_port}")
self.ui.lv_packets.setModel(self.packet_list_model)
def slot_disconnected_signal(self):
self.ui.page_divider.setEnabled(False)
self.ui.lbl_server_addr.setText(f"0.0.0.0:0")
self.ui.lbl_client_addr.setText(f"0.0.0.0:0")
def menu_btn_clicked(self):
btn = self.sender()
btnName = btn.objectName()
# show page_home page
if btnName == "btn_home":
self.ui.stackedWidget.setCurrentWidget(self.ui.page_home)
UIFunctions.resetStyle(self, btnName)
btn.setStyleSheet(UIFunctions.selectMenu(btn.styleSheet()))
# show settings page
if btnName == "btn_connection":
self.ui.stackedWidget.setCurrentWidget(self.ui.page_connection)
UIFunctions.resetStyle(self, btnName)
btn.setStyleSheet(UIFunctions.selectMenu(btn.styleSheet()))
# transmission page
if btnName == "btn_divider":
self.ui.stackedWidget.setCurrentWidget(self.ui.page_divider) # SET PAGE
UIFunctions.resetStyle(self, btnName) # RESET ANOTHERS BUTTONS SELECTED
btn.setStyleSheet(UIFunctions.selectMenu(btn.styleSheet())) # SELECT MENU
if self.server.ip is None or self.server.ip != self.ui.cb_server_ip.currentText() or self.server.port != self.ui.sb_server_port.value():
self.init_server()
self.server.start_listen_thread(self.ui.cb_server_ip.currentText(), self.ui.sb_server_port.value())
# show table page
if btnName == "btn_valvedata":
self.ui.stackedWidget.setCurrentWidget(self.ui.page_valvedata)
UIFunctions.resetStyle(self, btnName)
btn.setStyleSheet(UIFunctions.selectMenu(btn.styleSheet()))
def pb_send_clicked(self):
try:
index = int(self.sender().objectName()[-1])
cb_start: QComboBox = self.ui.__dict__[f"cb_start_{index}"]
cb_command: QComboBox = self.ui.__dict__[f"cb_command_{index}"]
cb_data: QComboBox = self.ui.__dict__[f"cb_data_{index}"]
cb_end: QComboBox = self.ui.__dict__[f"cb_end_{index}"]
start = unhexlify(cb_start.currentText().replace(" ", ""))
command = cb_command.currentText().replace(" ", "").encode("ascii").lower()
data = unhexlify(cb_data.currentText().replace(" ", ""))
end = unhexlify(cb_end.currentText().replace(" ", ""))
p = Protocol(start=start, cmd=command, data=data, end=end)
self.server.send_datagram(p.get_datagram())
except BaseException as e:
pass
def pb_divider_restore_clicked(self):
self.ui.le_preset_num.setText("1")
def pb_send_camera_clicked(self):
try:
abcd = self.sender().objectName()[-1]
sb_camera: QSpinBox = self.ui.__dict__[f"sb_camera_{abcd}"]
data = sb_camera.value()
p = Protocol(cmd=f"p{abcd}", data=f"{data:08d}".encode("ascii"))
self.server.send_datagram(p.get_datagram())
except BaseException as e:
pass
def manual_current_text_changed(self):
try:
index = int(self.sender().objectName()[-1])
cb_start: QComboBox = self.ui.__dict__[f"cb_start_{index}"]
cb_command: QComboBox = self.ui.__dict__[f"cb_command_{index}"]
cb_data: QComboBox = self.ui.__dict__[f"cb_data_{index}"]
cb_end: QComboBox = self.ui.__dict__[f"cb_end_{index}"]
start = unhexlify(cb_start.currentText().replace(" ", ""))
command = cb_command.currentText().replace(" ", "").encode("ascii").lower()
data = unhexlify(cb_data.currentText().replace(" ", ""))
end = unhexlify(cb_end.currentText().replace(" ", ""))
p = Protocol(start=start, cmd=command, data=data, end=end)
le_length: QLineEdit = self.ui.__dict__[f"le_length_{index}"]
le_check: QLineEdit = self.ui.__dict__[f"le_check_{index}"]
le_length.setText(str(p.get_length()))
le_check.setText(hexlify(p.get_check_bytes(), " ").decode("ascii"))
except BaseException as e:
le_length: QLineEdit = self.ui.__dict__[f"le_length_{index}"]
le_check: QLineEdit = self.ui.__dict__[f"le_check_{index}"]
le_length.setText("--")
le_check.setText("--")
def le_preset_num_text_changed(self):
index = int(self.ui.le_preset_num.text())
data = self.settings["page_divider"]["preset"]["data"][index - 1]
self.ui.le_preset_name.setText(data["le_preset_name"])
[self.ui.__dict__[f"sb_camera_{t}"].setValue(data[f"sb_camera_{t}"]) for t in "abcd"]
self.ui.sb_valve.setValue(data["sb_valve"])
self.ui.cb_from_camera.setCurrentText(data["cb_from_camera"])
self.ui.le_to_valve.setText(str(data["le_to_valve"]))
length = len(self.settings["page_divider"]["preset"]["data"])
self.ui.btn_preset_next.setEnabled(index != length)
self.ui.btn_preset_previous.setEnabled(index != 1)
def btn_preset_next_clicked(self):
last_index = int(self.ui.le_preset_num.text())
length = len(self.settings["page_divider"]["preset"]["data"])
if last_index < length:
last_index += 1
self.ui.le_preset_num.setText(str(last_index))
def btn_preset_previous_clicked(self):
last_index = int(self.ui.le_preset_num.text())
if last_index > 1:
last_index -= 1
self.ui.le_preset_num.setText(str(last_index))
def btn_preset_add_clicked(self):
data = self.settings["page_divider"]["preset"]["data"]
tmp_name = self.ui.le_preset_name.text()
tmp_name_adj = tmp_name.replace("(", r"\(").replace(")", r"\)")
num_list = []
for i in data:
l = re.findall(rf"^{tmp_name_adj}\s\((\d+)\)$", i["le_preset_name"])
if len(l) != 0:
num_list.append(int(l[-1]))
if len(num_list) != 0:
tmp_name += f" ({max(num_list) + 1})"
for i in data:
if tmp_name == i["le_preset_name"]:
tmp_name += " (1)"
break
new_dict = {
"le_preset_name": tmp_name,
"sb_camera_a": self.ui.sb_camera_a.value(),
"sb_camera_b": self.ui.sb_camera_b.value(),
"sb_camera_c": self.ui.sb_camera_c.value(),
"sb_camera_d": self.ui.sb_camera_d.value(),
"sb_valve": self.ui.sb_valve.value(),
"cb_from_camera": self.ui.cb_from_camera.currentText(),
"le_to_valve": int(self.ui.le_to_valve.text())
}
data.append(new_dict)
length = len(data)
self.ui.le_preset_name.setText(tmp_name)
self.ui.le_preset_num.setText(str(length))
def pb_send_camera_all_clicked(self):
try:
[self.server.send_datagram(Protocol(cmd=f"p{abcd}",
data=f"{self.ui.__dict__[f'sb_camera_{abcd}'].value():08d}".encode(
"ascii")).get_datagram()) for abcd in "abcd"]
except BaseException as e:
pass
def pb_send_start_clicked(self):
try:
p = Protocol(cmd=b"st", data=b"\xff")
self.server.send_datagram(p.get_datagram())
except BaseException as e:
pass
def pb_send_stop_clicked(self):
try:
p = Protocol(cmd=b"sp", data=b"\xff")
self.server.send_datagram(p.get_datagram())
except BaseException as e:
pass
def closeEvent(self, event: PySide6.QtGui.QCloseEvent) -> None:
super().closeEvent(event)
self.server.close()
with open(self.ui.le_profile_file.text(), "w") as f:
json.dump(self.settings, f)
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setWindowIcon(QIcon("icon.ico"))
window = MainWindow()
sys.exit(app.exec())