前端界面
自定义导航栏
重写窗口移动事件
def mousePressEvent(self, event):
# 重写窗口移动事件
if event.button() == Qt.LeftButton:
self.m_flag = True
self.m_Position = event.globalPos() - self.father.pos() # 获取鼠标相对窗口的位置
event.accept()
self.setCursor(QCursor(Qt.ArrowCursor)) # 更改鼠标图标 ArrowCursor为箭头
def mouseMoveEvent(self, QMouseEvent):
try:
if Qt.LeftButton and self.m_flag:
self.father.move(QMouseEvent.globalPos() - self.m_Position) # 更改窗口位置
QMouseEvent.accept()
except Exception:
pass
else:
pass
def mouseReleaseEvent(self, QMouseEvent):
self.m_flag = False
self.setCursor(QCursor(Qt.ArrowCursor))
重写窗口双击事件
def mouseDoubleClickEvent(self, QMouseEvent): # 双击事件
desktop = QApplication.desktop() # 获取屏幕宽度
if self.father.width() <= 750 or self.father.height() <= 410:
self.father.resize(desktop.width(), 450)
self.father.move(0, self.father.y()) # 最大化后将窗口移动至屏幕 x为0 y不变
else:
self.father.resize(750, 410)
self.father.move(desktop.width() / 2 - 400, self.father.y()) # 最小化后窗口居中
# 居中算法。屏幕宽度除2 就是屏幕的一半,在减软件窗口宽度一半等于居中
导航栏完整代码
# -*- coding: utf-8 -*-
# @Date : 2022/4/13
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor, QPixmap
from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QLabel, QPushButton
class TopWindow(QWidget):
def __init__(self, father=None):
super().__init__()
self.father = father
self.setFixedHeight(30) # 限制高度
layout = QHBoxLayout() # 水平布局
layout.setContentsMargins(10, 5, 10, 0) # 布局上下左右距离
layout.setSpacing(15) # 设置控件间距
self.setLayout(layout) # 布局加入窗口
minimize_button = QPushButton("─") # 最小化按钮
minimize_button.setFixedWidth(22)
maximize_button = QPushButton("☐") # 最大化按钮
maximize_button.setFixedWidth(22)
closure_button = QPushButton("✕") # 关闭窗口按钮
closure_button.setFixedWidth(22)
minimize_button.clicked.connect(lambda: self.father.showMinimized()) # 最小化按钮信号
maximize_button.clicked.connect(lambda: self.mouseDoubleClickEvent(None)) # 最大化按钮信号
closure_button.clicked.connect(lambda: self.father.close()) # 关闭窗口信号
logo = QLabel()
logo.setPixmap(QPixmap(':/product.png'))
logo.setMaximumSize(25, 25)
logo.setScaledContents(True) # label展示logo图片
title = QLabel("光电快门控制器")
title.setProperty('name', 'title')
title1 = QLabel("")
# title1.setStyleSheet(
# "QLabel{color:#000000}QLabel{font-family:'Microsoft YaHei';font-size:12px;}background-color:rgb(128,128,128)")
layout.addWidget(logo, 0, Qt.AlignLeft)
layout.addWidget(title, 0, Qt.AlignLeft)
layout.addWidget(title1, 1, Qt.AlignRight)
layout.addSpacing(30)
layout.addWidget(minimize_button, 0, Qt.AlignRight)
layout.addWidget(maximize_button, 0)
layout.addWidget(closure_button, 0)
self.setStyleSheet('''
QWidget>QPushButton{color: White;font-family:'微软雅黑';font-size:16px;border-radius:2px;}QWidget>QPushButton:hover{background:rgb(211,211,211);}
QLabel[name="title"]{color:#000000}QLabel{font-family:'SimHei';font-size:16px;}background-color:rgb(128,128,128)
''')
def mousePressEvent(self, event):
# 重写窗口移动事件
if event.button() == Qt.LeftButton:
self.m_flag = True
self.m_Position = event.globalPos() - self.father.pos() # 获取鼠标相对窗口的位置
event.accept()
self.setCursor(QCursor(Qt.ArrowCursor)) # 更改鼠标图标 ArrowCursor为箭头
def mouseMoveEvent(self, QMouseEvent):
try:
if Qt.LeftButton and self.m_flag:
self.father.move(QMouseEvent.globalPos() - self.m_Position) # 更改窗口位置
QMouseEvent.accept()
except Exception:
pass
else:
pass
def mouseReleaseEvent(self, QMouseEvent):
self.m_flag = False
self.setCursor(QCursor(Qt.ArrowCursor))
def mouseDoubleClickEvent(self, QMouseEvent): # 双击事件
desktop = QApplication.desktop() # 获取屏幕宽度
if self.father.width() <= 750 or self.father.height() <= 410:
self.father.resize(desktop.width(), 450)
self.father.move(0, self.father.y()) # 最大化后将窗口移动至屏幕 x为0 y不变
else:
self.father.resize(750, 410)
self.father.move(desktop.width() / 2 - 400, self.father.y()) # 最小化后窗口居中
# 居中算法。屏幕宽度除2 就是屏幕的一半,在减软件窗口宽度一半等于居中
# if __name__ == '__main__':
# app = QApplication(sys.argv)
# Li = TopWindow()
# Li.show()
# sys.exit(app.exec_())
前端样式美化,QSS
# -*- coding: utf-8 -*-
# @Date : 2022/4/13
from PyQt5.QtCore import Qt, QSize, pyqtSignal
from PyQt5.QtGui import QColor, QIcon, QPixmap
from PyQt5.QtWidgets import QVBoxLayout, QWidget, QLabel, QPushButton, QSpinBox, QTextEdit, QGroupBox, QComboBox, \
QCheckBox, QApplication
from photoelectric.fragments import imags
from photoelectric.fragments.draw_window import DrawWidget
from photoelectric.fragments.top_window import TopWindow
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("mainwindow")
MainWindow.setWindowFlags(Qt.FramelessWindowHint | Qt.Window) # 无边窗
MainWindow.setAttribute(Qt.WA_TranslucentBackground) # 设置窗口背景透明
MainWindow.setFixedHeight(450)
MainWindow.setMinimumSize(750, 410)
MainWindow.setWindowTitle("光电快门控制器")
widget = QWidget()
widget.setObjectName("widget")
MainWindow.setCentralWidget(widget)
layout = QVBoxLayout()
layout.setContentsMargins(5, 0, 5, 10)
widget.setLayout(layout)
top_window = TopWindow(MainWindow)
self.bottom_window = DrawWidget(color=QColor(211, 211, 211))
self.bottom_window.statusBar().showMessage(f"串口:未连接", 5000)
layout.addWidget(top_window)
layout.addWidget(self.bottom_window)
desktop = QApplication.desktop() # 获取屏幕宽度
self.logedit = QTextEdit(self.bottom_window)
self.logedit.setObjectName("logedit")
self.logedit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 隐藏竖直水平的进度条
self.logedit.setLineWrapMode(QTextEdit.NoWrap) # 显示水平滚动条
self.logedit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 隐藏竖直进度条
self.logedit.setReadOnly(True) # 文本内容 只读.不可编辑
self.logedit.setFixedHeight(130)
self.logedit.setFixedWidth(desktop.width()) # 最大宽度=屏幕宽度
self.logedit.move(240, 213)
self.edit = QTextEdit(self.bottom_window)
self.edit.setObjectName("edit")
self.edit.move(5, 5)
self.edit.resize(160, 50)
self.edit.setAlignment(Qt.AlignCenter) # 文本居中
self.edit.setReadOnly(True) # 文本内容 只读.不可编辑
manual_mode_label = QLabel("手动模式", self.bottom_window)
automatic_mode_label = QLabel("自动模式", self.bottom_window)
external_mode_label = QLabel("外部触发模式", self.bottom_window)
locking_label = QLabel("Locking", self.bottom_window)
log_label = QLabel("log", self.bottom_window)
serial_label = QLabel("串口选择", self.bottom_window)
open_label = QLabel("快门开:", self.bottom_window)
close_label = QLabel("快门关:", self.bottom_window)
cycle_label = QLabel("循环次数:", self.bottom_window)
external_label = QLabel("外部触发:", self.bottom_window)
self.open_spinBox = QSpinBox(self.bottom_window)
# self.open_spinBox.setKeyboardTracking(True) # 键盘跟踪
self.open_spinBox.setToolTip("开启时间")
self.close_spinBox = QSpinBox(self.bottom_window)
self.close_spinBox.setToolTip("关闭时间")
self.cycle_spinBox = QSpinBox(self.bottom_window)
self.cycle_spinBox.setToolTip("循环次数")
self.open_spinBox.setValue(2)
self.open_spinBox.setMinimum(1)
self.open_spinBox.setMaximum(1000)
self.open_spinBox.setFixedWidth(70)
self.close_spinBox.setValue(2)
self.close_spinBox.setMinimum(1)
self.close_spinBox.setMaximum(1000)
self.close_spinBox.setFixedWidth(70)
self.cycle_spinBox.setValue(10)
self.cycle_spinBox.setMinimum(1)
self.cycle_spinBox.setMaximum(1000)
self.cycle_spinBox.setFixedWidth(70)
hint_label = QLabel("时间单位:100ms", self.bottom_window)
hint_label.resize(120, 20)
self.information = QLabel(self.bottom_window)
manual_mode_label.move(500, 10)
automatic_mode_label.move(500, 45)
external_mode_label.move(500, 80)
locking_label.move(500, 110)
log_label.move(500, 138)
serial_label.move(500, 170)
open_label.move(5, 100)
close_label.move(5, 140)
cycle_label.move(5, 180)
external_label.move(5, 220)
hint_label.move(30, 60)
self.information.move(5, 280)
self.open_spinBox.move(100, 98)
self.close_spinBox.move(100, 138)
self.cycle_spinBox.move(100, 178)
box = GroupBox(self.bottom_window, 200)
box1 = GroupBox(self.bottom_window, 130)
box.move(185, 80)
box1.move(470, 5)
self.open_button = QPushButton(self.bottom_window)
self.open_button.setToolTip("打开")
self.stop_button = QPushButton(self.bottom_window)
self.stop_button.setToolTip("关闭")
# self.open_button.setEnabled(False)
# self.stop_button.setEnabled(False)
self.open_button.move(230, 100)
self.stop_button.move(360, 100)
self.stop_button.setMinimumSize(70, 70)
self.open_button.setMinimumSize(70, 70)
self.open_button.setIcon(QIcon(':/start.png'))
self.stop_button.setIcon(QIcon(':/stop.png'))
self.open_button.setIconSize(QSize(70, 70))
self.stop_button.setObjectName("but")
self.open_button.setObjectName("but")
self.stop_button.setIconSize(QSize(70, 70))
self.manual_mode_button = QPushButton(self.bottom_window)
self.manual_mode_button.setObjectName("mode_button")
self.manual_mode_button.setMaximumSize(30, 30)
self.manual_mode_button.setIcon(QIcon(':/no.png'))
self.manual_mode_button.setIconSize(QSize(30, 30))
self.manual_mode_button.setToolTip("手动模式")
self.automatic_mode_button = QPushButton(self.bottom_window)
self.automatic_mode_button.setObjectName("mode_button")
self.automatic_mode_button.setMaximumSize(30, 30)
self.automatic_mode_button.setIcon(QIcon(':/no.png')) # 按钮图片
self.automatic_mode_button.setIconSize(QSize(30, 30))
self.automatic_mode_button.setToolTip("自动模式")
self.external_mode_button = QPushButton(self.bottom_window)
self.external_mode_button.setObjectName("mode_button")
self.external_mode_button.setMaximumSize(30, 30)
self.external_mode_button.setIcon(QIcon(':/no.png')) # 按钮图片
self.external_mode_button.setIconSize(QSize(30, 30))
self.external_mode_button.setToolTip("外部触发模式")
self.locking_button = QCheckBox(self.bottom_window) # 物理锁
self.log_button = QCheckBox(self.bottom_window) # 日志锁
self.locking_button.move(629, 115)
self.log_button.move(629, 140)
self.manual_mode_button.move(620, 5)
self.automatic_mode_button.move(620, 40)
self.external_mode_button.move(620, 73)
self.hint_label = QLabel("串口未连接", self.bottom_window)
self.hint_label.move(270, 30)
self.combobox = ClickedComboBox(self.bottom_window)
self.combobox.move(580, 175)
self.combobox.resize(150, 23)
self.strspinbox = StrSpinBox(self.bottom_window)
self.strspinbox.setEnabled(False) # 默认锁定按钮
self.strspinbox.setFixedWidth(70)
self.strspinbox.setRange(0, 1) # 滚动长度
self.strspinbox.setWrapping(True) # 设置循环滚动
self.strspinbox.move(100, 218)
url_label = QLabel(self.bottom_window)
url_label.setObjectName("title")
url_label.move(2, 310)
url_label.setOpenExternalLinks(True)
url_label.setText(
'广州基座光学:'
'<a style="font-size:13px;font-family:微软雅黑;color:#FFA500;" href="http://www.oeabt.com/">'
'www.oeabt.com</a>'
'')
url_label.adjustSize()
logos = QLabel(self.bottom_window)
logos.setPixmap(QPixmap(':/oeabt.png'))
logos.setScaledContents(True)
logos.move(20, 270)
# mainwindow{background-color:qlineargradient(x1:0, y1:0 , x2:0 ,y2:1 stop:0 rgb(105,105,105) ,stop:0.1 rgb(255,161,27));}
MainWindow.setStyleSheet('''
#widget{background-color:qlineargradient(x1:0, y1:0 , x2:0 ,y2:1 stop:0 #99FFA500 ,stop:0.1 rgb(255,161,27));}
QComboBox:drop-down{border: none;subcontrol-position: right center;subcontrol-origin: padding;}
QComboBox:down-arrow{border: none;background: transparent;image: url(":/combobox.png");}
QComboBox{combobox-popup:0;height:25px;width:100px;border-style:none;font-size:12px;font-family:微软雅黑;color:rgba(0,0,0);border-radius:0px;}
QComboBox QAbstractItemView {color:DimGray;background:White;selection-color:rgba(0,0,0);selection-background-color: transparent;}
QSpinBox {padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 15px;border: 1px solid rgb(64,64,64);border-radius: 3px;color: rgb(200,200,200);background-color: rgb(44,44,44);selection-color: rgb(235,235,235);selection-background-color: rgb(83,121,180);font-family: "Microsoft Yahei";font-size: 10pt;}
QSpinBox:hover {background-color: rgb(105,105,105);}
QSpinBox::up-button { /* 向上按钮 */
subcontrol-origin: border; /* 起始位置 */
subcontrol-position: top right; /* 居于右上角 */
border: none; width: 12px;margin-top: 2px;margin-right: 3px;margin-bottom: 0px;}
QSpinBox::up-button:hover {border: none;}
QSpinBox::up-arrow { /* 向上箭头 */
image: url(:/s.png);}
QSpinBox::down-button { /* 向下按钮 */
subcontrol-origin: border;subcontrol-position: bottom right; border: none;width: 12px;margin-top: 0px;margin-right: 3px;margin-bottom: 2px;}
QSpinBox::down-button:hover { border: none;}
QSpinBox::down-arrow { /* 向下箭头 */
image: url(:/x.png);}
#edit{color:Black;font-family:'微软雅黑';font-size:18px;}
#logedit{color:Black;font-family:'微软雅黑';font-size:12px;}
QLabel{color: black;font-family:'微软雅黑';font-size:14px;}
#but{background:Transparent;border-radius:2px;}#but:hover{background:#C0C0C0;}
#mode_button{background:transparent;border-radius:2px;}#mode_button:hover{background:transparent;}
#title{color: black;font-family:'微软雅黑';font-size:12px;}
''')
self.locking_button.hide()
locking_label.hide()
self.logedit.hide()
class GroupBox(QGroupBox):
def __init__(self, father=None, height=35):
super().__init__(father)
self.height = height
self.setFixedWidth(2)
self.setFixedHeight(self.height) # 限定最大高度
self.setStyleSheet('QGroupBox::enabled{border: 2px solid DarkGray;}')
class StrSpinBox(QSpinBox):
def __init__(self, father=None):
super().__init__(father)
def textFromValue(self, v: int): # 自定义格式
shape = ['同步', '异步']
return shape[v]
class ClickedComboBox(QComboBox):
clicked = pyqtSignal() # 创建一个信号
def showPopup(self): # 重写showPopup函数
self.clicked.emit() # 发送信号
super(ClickedComboBox, self).showPopup() # 调用父类的showPopup()
# if __name__ == '__main__':
# app = QApplication(sys.argv)
# main = TransParentMainWindow()
# main.show()
# sys.exit(app.exec_())
本章重点1 监控 logging 日志:
import datetime
import logging
import os
# import sys
#
# from PyQt5.QtCore import Qt
# from PyQt5.QtGui import QFont
# from PyQt5.QtWidgets import QWidget, QTextEdit, QVBoxLayout, QApplication
from os import path
now = datetime.datetime.now()
otherStyleTime = now.strftime("%Y-%m-%d") # "%Y-%m-%d-%H-%M-%S"
user_path = f"{path.expanduser('~')}\\logs"
os.makedirs(user_path, exist_ok=True)
log_path = f"{user_path}\\{otherStyleTime}.log"
class ConsolePanelHandler(logging.Handler):
def __init__(self, parent):
logging.Handler.__init__(self)
self.parent = parent
def emit(self, record):
record_dict = record.__dict__
asctime = record_dict['asctime'] + " >> "
line = record_dict['filename'] + " -> line:" + str(record_dict['lineno']) + " | "
levelname = record_dict['levelname']
message = record_dict['message']
if levelname == 'ERROR':
color = "#FF0000"
elif levelname == 'WARNING':
color = "#FFD700"
else:
color = "#008000"
html = f'''
<div >
<span>{asctime}</span>
<span style="color:#4e4848;">{line.upper()}</span>
<span style="color: {color};">{levelname}</span>
<span style="color: #696969;">{message}</span>
</div>
'''
self.parent.write(html)
class Log:
def __init__(self, ):
self.logger = logging.getLogger(__name__)
self.logger.setLevel(level=20)
file_log = logging.FileHandler(log_path, encoding='utf-8')
formatter = logging.Formatter('%(asctime)s >> (%(filename)s[line:%(lineno)d]) | %(levelname)s: %(message)s - ',
'%Y-%m-%d %H:%M:%S')
file_log.setFormatter(formatter)
self.logger.addHandler(file_log)
def get_log(self):
return self.logger
#
logger = Log().get_log()
def clean_log(cleaningdays=1):
"""清理7天的文件"""
today = otherStyleTime.split("-")[-1]
for f in os.listdir(user_path):
if f.split(".")[-1] == "log":
try:
before = int(f[8:10])
except Exception as e:
logger.error(f"自动清理文件-{f}-失败-文件名必须已正确的日期格式命名 或 手动清理")
else:
if int(today) - before >= cleaningdays:
if os.path.exists(f"{user_path}\\{f}"): # 判断生成的路径对不对,防止报错、
os.remove(f"{user_path}\\{f}")
logger.info(f"自动清理旧文件:{f}")
# class Foo(QWidget):
# """测试类"""
#
# def __init__(self, parent=None):
# super().__init__(parent)
# self.textEdit = QTextEdit(self)
# self.textEdit.setLineWrapMode(QTextEdit.NoWrap)
# self.textEdit.setTextInteractionFlags(Qt.TextSelectableByMouse)
# vbox = QVBoxLayout()
# self.setLayout(vbox)
# vbox.addWidget(self.textEdit)
# handler = ConsolePanelHandler(self)
# logger.addHandler(handler)
# for i in range(100):
# logger.error("测试错误")
# logger.warning("警告")
# logger.info("正常")
#
# def write(self, s):
# self.textEdit.setFontWeight(QFont.Normal)
# self.textEdit.append(s)
#
#
# if __name__ == '__main__':
# app = QApplication(sys.argv)
# console_panel = Foo()
# console_panel.show()
# sys.exit(app.exec_())
本章重点2 串口通信:
获取串口:
def getserial_port_name(self):
"""获取串口"""
serial_list = []
port_list = list(serial.tools.list_ports.comports())
for p in port_list:
serial_list.append(p.__str__())
#self.combobox.clear()#清空列表
#self.combobox.addItems(serial_list)#载入最新串口名
#logger.info(f"获取串口{str(serial_list)}") # 获取串口后记录日志
打开串口:
def combobox_connect(self, serial_name):
try:
com = self.open_serial(serial_name) # 打开串口
except Exception as e:
logger.error(f"{e.__str__()}")
else:
if com:
self.hint_label.setText(f"串口连接成功")
self.hint_label.adjustSize()
self.bottom_window.statusBar().showMessage(f"串口:{serial_name}- 连接成功", 5000)
self.com = com
self.serialmonitor.com = self.com
self.serialmonitor.start() # 开启线程
self.setshutter(open_nums=self.open_spinBox.value(), close_nums=self.close_spinBox.value(),
cycle_nums=self.cycle_spinBox.value()) # 初始化快门设置
logger.info(
f"设置快门数值:快门开:{self.open_spinBox.value()}-快门关:{self.close_spinBox.value()}-循环次数:{self.cycle_spinBox.value()}")
logger.info(f"串口:{serial_name}- 连接成功")
def open_serial(self, serial_name):
"""打开串口"""
serial_com = serial_name.split("-")[0]
try:
com = serial.Serial(serial_com, 115200, timeout=0.5)
except Exception as e:
logger.error(e.__str__())
else:
if com.isOpen():
logger.info(f"串口状况:{com.__str__()}")
return com
return False
向串口发送信息:
def send_take_over(self, i):
"""发送"""
instruction = bytes.fromhex(i)
try:
self.com.write(instruction) # 发送
except Exception as e:
logger.error(e.__str__())
接收串口数据:
需要创建子线程
class SerialMonitor(QThread):
SerialMonitor_signal = pyqtSignal(list) # 自定义信号
def __init__(self):
super().__init__()
self.com = None
def send_take_over(self, i):
"""发送"""
instruction = bytes.fromhex(i)
try:
self.com.write(instruction) # 发送
except Exception as e:
logger.error(e.__str__())
def run(self):
none_list = []
while True:
try:
datas = str(binascii.b2a_hex(self.com.read(24)))[2:-1] # 接收
except Exception as e:
self.com.close() # 关闭串口
logger.error(e.__str__())
break
else:
if datas == '':
none_list.append(None)
else:
if re.findall("000046", datas): # 心跳包
self.send_take_over("4F 4B 21")
elif re.findall("4f4b21", datas): # 接收OK
self.send_take_over("4F 4B 21")
elif re.findall("000043", datas): # 手动模式
self.send_take_over("4F 4B 21")
self.SerialMonitor_signal.emit([f"触发次数:{str(int(datas[24:26], 16))}"]) # 手动模式信号
elif re.findall("000044", datas): # 自动模式
self.send_take_over("4F 4B 21")
self.SerialMonitor_signal.emit([f"循环次数:{str(int(datas[24:26], 16))}"]) # 自动模式信号
elif re.findall("000045", datas): # 外部触发
self.send_take_over("4F 4B 21")
self.SerialMonitor_signal.emit([f"触发次数:{str(int(datas[24:26], 16))}"]) # 外部触发信号
none_list.clear() # 清空计数列表
if len(none_list) > 5:
self.com.close() # 关闭串口
self.SerialMonitor_signal.emit([None])
break
校验:
def setshutter(self, open_nums, close_nums, cycle_nums):
"""设置快门"""
open_num = hex(open_nums).upper()[2:]
close_num = hex(close_nums).upper()[2:]
cycle_num = hex(cycle_nums).upper()[2:]
if len(open_num) <= 1:
open_num = '0' + open_num
if len(close_num) <= 1:
close_num = '0' + close_num
if len(cycle_num) <= 1:
cycle_num = '0' + cycle_num
check_data = f'8E 01 7A 00 00 00 00 00 41 00 {open_num} 00 {close_num} 00 {cycle_num} 00 00 00 00 00 0A '
try:
self.send_take_over(check_data + self.get_CRC_16_value(check_data) + ' 8E')
except Exception as e:
logger.error(e.__str__())
else:
logger.info(f"快门设置:{check_data + self.get_CRC_16_value(check_data) + ' 8E'}")
def get_CRC_16_value(self, datas):
"""进制转换"""
crc16 = mkCrcFun(0x11021, rev=False, initCrc=0x0000, xorOut=0x0000)
data = datas.replace(' ', '')
crc_out = hex(crc16(unhexlify(data))).upper()
str_list = list(crc_out)
if len(str_list) == 5:
str_list.insert(2, '0') # 位数不足补0
crc_data = ''.join(str_list[2:])
return crc_data[:2] + ' ' + crc_data[2:]
完整代码:
# -*- coding: utf-8 -*-
# @Date : 2022/4/14
import binascii
import re
from functools import partial
from binascii import unhexlify
import time
from PyQt5.QtGui import QIcon, QTextCursor
from crcmod import mkCrcFun
from PyQt5.QtCore import QThread, pyqtSignal, QTimer
from PyQt5.QtWidgets import QMessageBox, QMainWindow
import serial.tools.list_ports
from photoelectric.fragments.log import ConsolePanelHandler, logger, clean_log
from photoelectric.fragments.windows import Ui_MainWindow
class Main(QMainWindow, Ui_MainWindow):
def __init__(self):
super(Main, self).__init__()
self.com = None # 初始化窗口对象
handler = ConsolePanelHandler(self) # 初始化日志
logger.addHandler(handler) # 声明日志输出
self.external_name = ["同步", "异步"]
self.setupUi(self) # 初始布局
self.setWindowIcon(QIcon(':product.png')) # 窗口图标。 用于子窗口继承
self.manual_mode_button.clicked.connect(lambda: self.mode(self.manual_mode_button)) # 手动模式
self.automatic_mode_button.clicked.connect(lambda: self.mode(self.automatic_mode_button)) # 自动模式
self.external_mode_button.clicked.connect(lambda: self.mode(self.external_mode_button)) # 外部模式
self.log_button.stateChanged.connect(lambda: self.log_button_connect(self.log_button)) # log 选框
self.locking_button.stateChanged.connect(lambda: self.locking_button_connect(self.locking_button)) # 物理锁
self.stop_button.clicked.connect(partial(self.button_connect, self.stop_button)) # 启动按钮
self.open_button.clicked.connect(partial(self.button_connect, self.open_button)) # 暂停按钮
self.combobox.activated[str].connect(self.combobox_connect) # 选择信号
self.combobox.clicked.connect(lambda: self.getserial_port_name()) # 点击信号重新获取串口
self.open_spinBox.editingFinished.connect(self.pinBox_connect) # 快门开
self.close_spinBox.editingFinished.connect(self.pinBox_connect) # 快门关
self.cycle_spinBox.editingFinished.connect(self.pinBox_connect) # 循环次数
self.strspinbox.valueChanged.connect(self.strspinbox_connect) # 同步 异步
self.lock = True
logger.info("登录")
self.getserial_port_name() # 获取串口
clean_log(7) # 清理旧日志 7天以上
self.serialmonitor = SerialMonitor()
self.serialmonitor.SerialMonitor_signal.connect(self.returndata)
def returndata(self, data):
"""心跳监控"""
if data[0] is None:
logger.error("控制器无响应")
QMessageBox.critical(self, "错误", "控制器无响应")
return
self.edit.setText(data[0])
if data[0] == '循环次数:0':
self.init_button()
def write(self, s):
"""日志"""
self.logedit.append(s)
self.logedit.moveCursor(QTextCursor.End) # 光标末尾
self.logedit.moveCursor(QTextCursor.StartOfLine) # 光标换行
def locking_button_connect(self, t):
if t.isChecked(): # 是否被选择
pass
def log_button_connect(self, t):
if t.isChecked(): # 是否被选择
self.logedit.show()
logger.info("显示日志")
return
self.logedit.hide()
logger.info("隐藏日志")
def pinBox_connect(self):
"""快门设置信号"""
self.setshutter(open_nums=self.open_spinBox.value(), close_nums=self.close_spinBox.value(),
cycle_nums=self.cycle_spinBox.value())
logger.info(
f"更新快门数值:快门开:{self.open_spinBox.value()}-快门关:{self.close_spinBox.value()}-循环次数:{self.cycle_spinBox.value()}")
def strspinbox_connect(self):
if self.external_name[self.strspinbox.value()] == "同步":
synchronize = '8E 01 7A 00 00 00 00 00 42 03 00 00 00 00 00 00 00 00 00 00 0A C9 2E 8E'
self.send_take_over(synchronize)
elif self.external_name[self.strspinbox.value()] == "异步":
asynchronous = '8E 01 7A 00 00 00 00 00 42 04 00 00 00 00 00 00 00 00 00 00 0A C1 65 8E'
self.send_take_over(asynchronous)
logger.info(f"切换外部触发模式({self.external_name[self.strspinbox.value()]})")
def mode(self, b):
if self.com is None:
logger.warning(f"点击---{b.toolTip()}-无效 串口未连接")
QMessageBox.critical(self, "错误", "串口未连接")
else:
if not self.com.isOpen():
logger.warning(f"点击---{b.toolTip()}-无效 串口未打开")
QMessageBox.critical(self, "错误", "串口未打开")
else:
if b.toolTip() == "手动模式" and self.lock:
self.exclusive(b) # 排他
self.init_button() # 初始按钮
self.hint_label.setText("当前为:(手动模式)") # 更换内容
self.hint_label.adjustSize()
enter_manual_mode = '8E 01 7A 00 00 00 00 00 42 01 00 00 00 00 00 00 00 00 00 00 0A CF C4 8E'
self.send_take_over(enter_manual_mode)
logger.info(f"点击---{b.toolTip()}")
elif b.toolTip() == "自动模式" and self.lock:
self.exclusive(b) # 排他
self.init_button() # 初始按钮
self.hint_label.setText("当前为:(自动模式)")
self.hint_label.adjustSize()
enter_automatic_mode = '8E 01 7A 00 00 00 00 00 42 02 00 00 00 00 00 00 00 00 00 00 0A CA 5B 8E'
self.send_take_over(enter_automatic_mode)
logger.info(f"点击---{b.toolTip()}")
elif b.toolTip() == "外部触发模式" and self.lock:
self.strspinbox.setEnabled(True) # 同步, 异步按钮解锁
self.exclusive(b) # 排他
self.open_button.setEnabled(False) # 锁定启动按钮
self.stop_button.setEnabled(False) # 锁定关闭按钮
self.hint_label.setText("当前为:(外部触发模式)")
self.hint_label.adjustSize()
self.strspinbox_connect() # 切换外部触发
logger.info(f"点击---{b.toolTip()}---{self.external_name[self.strspinbox.value()]}")
else:
logger.warning(f"点击---{b.toolTip()}-无效 自动模式运行中禁止切换模式")
QMessageBox.critical(self, "错误", "自动模式运行中禁止切换模式")
def exclusive(self, ob):
obj = [self.manual_mode_button, self.automatic_mode_button, self.external_mode_button]
for o in obj:
if o is ob:
o.setIcon(QIcon(':/ok.png'))
else:
o.setIcon(QIcon(':/no.png'))
def init_button(self):
self.open_button.setEnabled(True)
self.stop_button.setEnabled(False)
self.lock = True # 解锁
self.strspinbox.setEnabled(False) # 锁定异步 同步
def button_connect(self, obj):
if self.com is None:
logger.warning(f"点击---{obj.toolTip()}-无效 串口未连接")
QMessageBox.critical(self, "错误", "串口未连接")
else:
if not self.com.isOpen():
logger.warning(f"点击---{obj.toolTip()}-无效 串口未打开")
QMessageBox.critical(self, "错误", "串口未打开")
else:
if self.hint_label.text() == "当前为:(手动模式)":
if obj is self.open_button:
self.open_button.setEnabled(False)
self.stop_button.setEnabled(True)
turn_on_manual_mode = '8E 01 7A 00 00 00 00 00 43 01 00 00 00 00 00 00 00 00 00 00 0A 8A A7 8E'
self.send_take_over(turn_on_manual_mode)
logger.info(f"开启--{self.hint_label.text()}")
else:
self.init_button() #
stop_manual_mode = '8E 01 7A 00 00 00 00 00 43 00 00 00 00 00 00 00 00 00 00 00 0A 89 D2 8E'
self.send_take_over(stop_manual_mode)
logger.info(f"暂停--{self.hint_label.text()}")
elif self.hint_label.text() == "当前为:(自动模式)":
if obj is self.open_button:
self.lock = False # 加锁 ,运行状态下不允许切换模式
self.open_button.setEnabled(False)
self.stop_button.setEnabled(True)
turn_on_automatic_mode = '8E 01 7A 00 00 00 00 00 44 01 00 00 00 00 00 00 00 00 00 00 0A 40 AF 8E'
self.send_take_over(turn_on_automatic_mode)
logger.info(f"开启--{self.hint_label.text()}")
else:
self.init_button() # 解锁 初始化按钮
stop_automatic_mode = '8E 01 7A 00 00 00 00 00 44 00 00 00 00 00 00 00 00 00 00 00 0A 43 DA 8E'
self.send_take_over(stop_automatic_mode) # 发送指令
logger.info(f"暂停--{self.hint_label.text()}")
else:
logger.warning(f"点击---{obj.toolTip()}-无效 未选择模式")
QMessageBox.critical(self, "错误", "未选择模式")
def getserial_port_name(self):
"""获取串口"""
serial_list = []
port_list = list(serial.tools.list_ports.comports())
for p in port_list:
serial_list.append(p.__str__())
self.combobox.clear()
self.combobox.addItems(serial_list)
logger.info(f"获取串口{str(serial_list)}")
def combobox_connect(self, serial_name):
try:
com = self.open_serial(serial_name) # 打开串口
except Exception as e:
logger.error(f"{e.__str__()}")
else:
if com:
self.hint_label.setText(f"串口连接成功")
self.hint_label.adjustSize()
self.bottom_window.statusBar().showMessage(f"串口:{serial_name}- 连接成功", 5000)
self.com = com
self.serialmonitor.com = self.com
self.serialmonitor.start() # 开启线程
self.setshutter(open_nums=self.open_spinBox.value(), close_nums=self.close_spinBox.value(),
cycle_nums=self.cycle_spinBox.value()) # 初始化快门设置
logger.info(
f"设置快门数值:快门开:{self.open_spinBox.value()}-快门关:{self.close_spinBox.value()}-循环次数:{self.cycle_spinBox.value()}")
logger.info(f"串口:{serial_name}- 连接成功")
def open_serial(self, serial_name):
"""打开串口"""
serial_com = serial_name.split("-")[0]
try:
com = serial.Serial(serial_com, 115200, timeout=0.5)
except Exception as e:
logger.error(e.__str__())
else:
if com.isOpen():
logger.info(f"串口状况:{com.__str__()}")
return com
return False
def send_take_over(self, i):
"""发送"""
instruction = bytes.fromhex(i)
try:
self.com.write(instruction) # 发送
except Exception as e:
logger.error(e.__str__())
def setshutter(self, open_nums, close_nums, cycle_nums):
"""设置快门"""
open_num = hex(open_nums).upper()[2:]
close_num = hex(close_nums).upper()[2:]
cycle_num = hex(cycle_nums).upper()[2:]
if len(open_num) <= 1:
open_num = '0' + open_num
if len(close_num) <= 1:
close_num = '0' + close_num
if len(cycle_num) <= 1:
cycle_num = '0' + cycle_num
check_data = f'8E 01 7A 00 00 00 00 00 41 00 {open_num} 00 {close_num} 00 {cycle_num} 00 00 00 00 00 0A '
try:
self.send_take_over(check_data + self.get_CRC_16_value(check_data) + ' 8E')
except Exception as e:
logger.error(e.__str__())
else:
logger.info(f"快门设置:{check_data + self.get_CRC_16_value(check_data) + ' 8E'}")
def get_CRC_16_value(self, datas):
"""进制转换"""
crc16 = mkCrcFun(0x11021, rev=False, initCrc=0x0000, xorOut=0x0000)
data = datas.replace(' ', '')
crc_out = hex(crc16(unhexlify(data))).upper()
str_list = list(crc_out)
if len(str_list) == 5:
str_list.insert(2, '0') # 位数不足补0
crc_data = ''.join(str_list[2:])
return crc_data[:2] + ' ' + crc_data[2:]
class SerialMonitor(QThread):
SerialMonitor_signal = pyqtSignal(list) # 自定义信号
def __init__(self):
super().__init__()
self.com = None
def send_take_over(self, i):
"""发送"""
instruction = bytes.fromhex(i)
try:
self.com.write(instruction) # 发送
except Exception as e:
logger.error(e.__str__())
def run(self):
none_list = []
while True:
try:
datas = str(binascii.b2a_hex(self.com.read(24)))[2:-1] # 接收
except Exception as e:
self.com.close() # 关闭串口
logger.error(e.__str__())
break
else:
if datas == '':
none_list.append(None)
else:
if re.findall("000046", datas): # 心跳包
self.send_take_over("4F 4B 21")
elif re.findall("4f4b21", datas): # 接收OK
self.send_take_over("4F 4B 21")
elif re.findall("000043", datas): # 手动模式
self.send_take_over("4F 4B 21")
self.SerialMonitor_signal.emit([f"触发次数:{str(int(datas[24:26], 16))}"]) # 手动模式信号
elif re.findall("000044", datas): # 自动模式
self.send_take_over("4F 4B 21")
self.SerialMonitor_signal.emit([f"循环次数:{str(int(datas[24:26], 16))}"]) # 自动模式信号
elif re.findall("000045", datas): # 外部触发
self.send_take_over("4F 4B 21")
self.SerialMonitor_signal.emit([f"触发次数:{str(int(datas[24:26], 16))}"]) # 外部触发信号
none_list.clear() # 清空计数列表
if len(none_list) > 5:
self.com.close() # 关闭串口
self.SerialMonitor_signal.emit([None])
break
# if __name__ == '__main__':
# app = QApplication(sys.argv)
# main = Main()
# main.show()
# sys.exit(app.exec_())
绘制子窗口:
# -*- coding: utf-8 -*-
# @Date : 2022/4/12
from PyQt5.QtCore import Qt, QRect, QRectF
from PyQt5.QtGui import QColor, QPainter, QImage, QPainterPath, QBrush
from PyQt5.QtWidgets import QMainWindow
class DrawWidget(QMainWindow):
def __init__(self, parent=None, color=QColor(105, 105, 105)):
super().__init__(parent)
# 设置 窗口无边框和背景透明
self.setAttribute(Qt.WA_TranslucentBackground)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.Window)
self.border_width = 0 # 阴影
self.background = color
def paintEvent(self, event):
path = QPainterPath()
path.setFillRule(Qt.WindingFill)
pat = QPainter(self)
pat.setRenderHint(pat.Antialiasing)
pat.fillPath(path, QBrush(Qt.white))
color = QColor(255, 255, 255, 50)
i_path = QPainterPath()
i_path.setFillRule(Qt.WindingFill)
ref = QRectF(1, 1, self.width() - 1 * 2, self.height() - 1 * 2)
# i_path.addRect(ref)
i_path.addRoundedRect(ref, self.border_width, self.border_width)
color.setAlpha(int(150 - 1 ** 0.5 * 50))
pat.setPen(color)
pat.drawPath(i_path)
pat2 = QPainter(self)
pat2.setRenderHint(pat2.Antialiasing) # 抗锯齿
pat2.setBrush(self.background)
pat2.setPen(Qt.transparent)
rect = self.rect()
rect.setLeft(0)
rect.setTop(0)
rect.setWidth(rect.width())
rect.setHeight(rect.height())
pat2.drawRoundedRect(rect, 3, 3)
# 绘制图像
image = QImage(':/Logos.png')
rect = QRect(self.width() - 400, self.height() - 130, image.width() // 1, image.height() // 1)
painter = QPainter()
painter.begin(self)
painter.drawImage(rect, image)
painter.end()
#
# if __name__ == '__main__':
# app = QApplication(sys.argv)
# Li = DrawWidget()
# Li.resize(500, 500)
# Li.show()
# sys.exit(app.exec_())
整体目录结构: