2025-04-11 19:23:11 +08:00
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
import json
|
|
|
|
import socket
|
|
|
|
import base64
|
|
|
|
import shutil
|
|
|
|
import signal
|
|
|
|
import hashlib
|
|
|
|
import inspect
|
|
|
|
import tempfile
|
|
|
|
import platform
|
|
|
|
import subprocess
|
|
|
|
from pathlib import Path
|
|
|
|
from PyQt5.QtWidgets import QApplication, QMessageBox, QMenu, QAction, QWidget, QVBoxLayout, QTableWidget, QTableWidgetItem, QLabel, QFrame, QLineEdit, QMainWindow, QDesktopWidget, QHBoxLayout, QPushButton, QDialog
|
|
|
|
from PyQt5.QtCore import Qt, QCoreApplication, QDateTime, QTimer
|
|
|
|
from PyQt5.QtGui import QFont, QPalette, QColor, QBrush, QIcon
|
|
|
|
|
|
|
|
__file__ = os.path.abspath(sys.argv[0])
|
|
|
|
|
|
|
|
|
|
|
|
def _fd(f):
|
|
|
|
return f.fileno() if hasattr(f, 'fileno') else f
|
|
|
|
|
|
|
|
|
|
|
|
if os.name == 'nt':
|
|
|
|
import msvcrt
|
|
|
|
from ctypes import (sizeof, c_ulong, c_void_p, c_int64, Structure, Union, POINTER, windll, byref)
|
|
|
|
from ctypes.wintypes import BOOL, DWORD, HANDLE
|
|
|
|
|
|
|
|
LOCK_SH = 0x0
|
|
|
|
LOCK_NB = 0x1
|
|
|
|
LOCK_EX = 0x2
|
|
|
|
LOCK_UN = 0x9
|
|
|
|
|
|
|
|
if sizeof(c_ulong) != sizeof(c_void_p):
|
|
|
|
ULONG_PTR = c_int64
|
|
|
|
else:
|
|
|
|
ULONG_PTR = c_ulong
|
|
|
|
|
|
|
|
PVOID = c_void_p
|
|
|
|
|
|
|
|
|
|
|
|
class _OFFSET(Structure):
|
|
|
|
_fields_ = [
|
|
|
|
('Offset', DWORD),
|
|
|
|
('OffsetHigh', DWORD)
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
class _OFFSET_UNION(Union):
|
|
|
|
_fields_ = [
|
|
|
|
('_offset', _OFFSET),
|
|
|
|
('Pointer', PVOID)
|
|
|
|
]
|
|
|
|
_anonymous_ = ['_offset']
|
|
|
|
|
|
|
|
|
|
|
|
class OVERLAPPED(Structure):
|
|
|
|
_fields_ = [
|
|
|
|
('Internal', ULONG_PTR),
|
|
|
|
('InternalHigh', ULONG_PTR),
|
|
|
|
('_offset_union', _OFFSET_UNION),
|
|
|
|
('hEvent', HANDLE)
|
|
|
|
]
|
|
|
|
_anonymous_ = ['_offset_union']
|
|
|
|
|
|
|
|
|
|
|
|
LPOVERLAPPED = POINTER(OVERLAPPED)
|
|
|
|
LockFileEx = windll.kernel32.LockFileEx
|
|
|
|
LockFileEx.restype = BOOL
|
|
|
|
LockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, DWORD, LPOVERLAPPED]
|
|
|
|
UnlockFileEx = windll.kernel32.UnlockFileEx
|
|
|
|
UnlockFileEx.restype = BOOL
|
|
|
|
UnlockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, LPOVERLAPPED]
|
|
|
|
|
|
|
|
|
|
|
|
def flock(f, flags):
|
|
|
|
hfile = msvcrt.get_osfhandle(_fd(f))
|
|
|
|
overlapped = OVERLAPPED()
|
|
|
|
if flags == LOCK_UN:
|
|
|
|
ret = UnlockFileEx(
|
|
|
|
hfile,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0xFFFF0000,
|
|
|
|
byref(overlapped)
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
ret = LockFileEx(
|
|
|
|
hfile,
|
|
|
|
flags,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0xFFFF0000,
|
|
|
|
byref(overlapped)
|
|
|
|
)
|
|
|
|
return bool(ret)
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
import fcntl
|
|
|
|
|
|
|
|
LOCK_SH = fcntl.LOCK_SH
|
|
|
|
LOCK_NB = fcntl.LOCK_NB
|
|
|
|
LOCK_EX = fcntl.LOCK_EX
|
|
|
|
LOCK_UN = fcntl.LOCK_UN
|
|
|
|
except (ImportError, AttributeError):
|
|
|
|
LOCK_EX = LOCK_SH = LOCK_NB = 0
|
|
|
|
|
|
|
|
|
|
|
|
def flock(f, flags):
|
|
|
|
return flags == LOCK_UN
|
|
|
|
else:
|
|
|
|
def flock(f, flags):
|
|
|
|
return fcntl.flock(_fd(f), flags) == 0
|
|
|
|
|
|
|
|
|
|
|
|
class SafeJSONConfigReader:
|
|
|
|
def __init__(self, file: str):
|
|
|
|
try:
|
|
|
|
self.data = json.loads(open(file=os.path.abspath(file), mode='r', encoding='utf-8').read())
|
|
|
|
except (json.JSONDecodeError, FileNotFoundError, PermissionError):
|
|
|
|
self.data = None
|
|
|
|
except (Exception,):
|
|
|
|
self.data = None
|
|
|
|
|
|
|
|
def is_loaded_success(self):
|
|
|
|
return self.data is not None
|
|
|
|
|
|
|
|
def get(self, key, default=None):
|
|
|
|
if (self.data is None) == 1:
|
|
|
|
return default
|
|
|
|
keys = key.split('.')
|
|
|
|
value = self.data
|
|
|
|
try:
|
|
|
|
for k in keys:
|
|
|
|
if isinstance(value, dict):
|
|
|
|
value = value.get(k)
|
|
|
|
else:
|
|
|
|
return default
|
|
|
|
if (value is None) == 1:
|
|
|
|
return default
|
|
|
|
return value
|
|
|
|
except (AttributeError, TypeError):
|
|
|
|
return default
|
|
|
|
|
|
|
|
|
|
|
|
class CustomLineEdit(QLineEdit):
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
super().__init__(parent)
|
|
|
|
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
|
|
self.customContextMenuRequested.connect(self.show_context_menu)
|
|
|
|
|
|
|
|
def show_context_menu(self, pos):
|
|
|
|
menu = QMenu(self)
|
|
|
|
action_c = menu.addAction('复制')
|
|
|
|
action_c.triggered.connect(self.copy_text_content)
|
|
|
|
action_c.setEnabled(self.hasSelectedText())
|
|
|
|
action_p = menu.addAction('粘贴')
|
|
|
|
action_p.triggered.connect(self.paste)
|
|
|
|
action_p.setEnabled(self.isEnabled())
|
|
|
|
menu.exec_(self.mapToGlobal(pos))
|
|
|
|
|
|
|
|
def copy_text_content(self):
|
|
|
|
self.hasSelectedText() and self.copy()
|
|
|
|
|
|
|
|
|
|
|
|
class MessageBox(QDialog):
|
|
|
|
def __init__(self, title, text, parent=None):
|
|
|
|
super().__init__(parent)
|
|
|
|
self.setWindowTitle(title)
|
|
|
|
dark_palette = QPalette()
|
|
|
|
dark_palette.setColor(QPalette.Window, QColor(53, 53, 53))
|
|
|
|
dark_palette.setColor(QPalette.WindowText, Qt.white)
|
|
|
|
dark_palette.setColor(QPalette.Base, QColor(35, 35, 35))
|
|
|
|
dark_palette.setColor(QPalette.Text, Qt.white)
|
|
|
|
dark_palette.setColor(QPalette.Button, QColor(53, 53, 53))
|
|
|
|
dark_palette.setColor(QPalette.ButtonText, Qt.white)
|
|
|
|
self.setPalette(dark_palette)
|
|
|
|
self.setStyleSheet("""
|
|
|
|
QPushButton {
|
|
|
|
background: #444;
|
|
|
|
padding: 8px 16px;
|
|
|
|
border-radius: 4px;
|
|
|
|
color: white;
|
|
|
|
min-width: 80px;
|
|
|
|
}
|
|
|
|
QPushButton:hover {
|
|
|
|
background: #555;
|
|
|
|
}
|
|
|
|
QLabel {
|
|
|
|
color: white;
|
|
|
|
}
|
|
|
|
""")
|
|
|
|
main_layout = QVBoxLayout(self)
|
|
|
|
main_layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
main_layout.setSpacing(20)
|
|
|
|
self.labels_content = QLabel(text)
|
|
|
|
self.labels_content.setFont(QFont('Consolas', 12))
|
|
|
|
self.labels_content.setWordWrap(True)
|
|
|
|
self.labels_content.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
|
|
main_layout.addWidget(self.labels_content, 1)
|
|
|
|
|
|
|
|
self.button_confirm = QPushButton('确定')
|
|
|
|
self.button_confirm.setFont(QFont('Microsoft YaHei', 12))
|
|
|
|
self.button_confirm.clicked.connect(self.accept)
|
|
|
|
main_layout.addWidget(self.button_confirm, 0, Qt.AlignCenter)
|
|
|
|
|
|
|
|
self.button_confirm.setFocus()
|
|
|
|
|
|
|
|
self.adjustSize()
|
|
|
|
self.setFixedSize(self.size())
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def show_message(cls, title, text, parent=None):
|
|
|
|
return cls(title, text, parent).exec_()
|
|
|
|
|
|
|
|
|
|
|
|
class TextInputDialog(QDialog):
|
|
|
|
def __init__(self, title, default_text, regexp, placeholder, parent=None):
|
|
|
|
super().__init__(parent)
|
|
|
|
self.setWindowTitle('%s' % (title,))
|
|
|
|
self.setFixedSize(450, 155)
|
|
|
|
self.regexp = regexp
|
|
|
|
dark_palette = QPalette()
|
|
|
|
dark_palette.setColor(QPalette.Window, QColor(53, 53, 53))
|
|
|
|
dark_palette.setColor(QPalette.WindowText, Qt.white)
|
|
|
|
dark_palette.setColor(QPalette.Base, QColor(35, 35, 35))
|
|
|
|
dark_palette.setColor(QPalette.Text, Qt.white)
|
|
|
|
self.setPalette(dark_palette)
|
|
|
|
self.setStyleSheet("""
|
|
|
|
QLineEdit {
|
|
|
|
background: #2a2a2a;
|
|
|
|
border: 1px solid #444;
|
|
|
|
border-radius: 3px;
|
|
|
|
padding: 5px;
|
|
|
|
color: white;
|
|
|
|
}
|
|
|
|
""")
|
|
|
|
main_layout = QVBoxLayout(self)
|
|
|
|
main_layout.setContentsMargins(15, 15, 15, 15)
|
|
|
|
self.inputs_text = CustomLineEdit(default_text)
|
|
|
|
self.inputs_text.setFont(QFont('Consolas', 14))
|
|
|
|
self.inputs_text.setPlaceholderText(placeholder)
|
|
|
|
self.inputs_text.returnPressed.connect(self.validate_and_accept)
|
|
|
|
self.inputs_text.selectAll()
|
|
|
|
main_layout.addWidget(self.inputs_text)
|
|
|
|
|
|
|
|
def validate_and_accept(self):
|
|
|
|
text = self.inputs_text.text().strip()
|
|
|
|
if (self.regexp == '' or bool(re.match(self.regexp, text))) == 1:
|
|
|
|
self.accept()
|
|
|
|
else:
|
|
|
|
self.inputs_text.setStyleSheet('border: 1px solid red;')
|
|
|
|
self.inputs_text.setFocus()
|
|
|
|
|
|
|
|
def text(self):
|
|
|
|
return self.inputs_text.text().strip()
|
|
|
|
|
|
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
|
|
def __init__(self, app_name, app_version):
|
|
|
|
super().__init__()
|
|
|
|
self.app_name = app_name
|
|
|
|
self.app_version = app_version
|
|
|
|
self.operators = 'Unknown'
|
|
|
|
self.equipment = 'Unknown'
|
|
|
|
self.host = None
|
|
|
|
self.port = None
|
|
|
|
self.station = None
|
|
|
|
self.scan_regexp = None
|
|
|
|
self.scanning_record = []
|
|
|
|
self.scanning_latest = []
|
|
|
|
self.setWindowTitle('%s %s' % (self.app_name, self.app_version))
|
|
|
|
self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), 'favicon.ico')))
|
|
|
|
self.setMinimumSize(925, 728)
|
|
|
|
|
|
|
|
frame = self.frameGeometry()
|
|
|
|
center_point = QDesktopWidget().availableGeometry().center()
|
|
|
|
frame.moveCenter(center_point)
|
|
|
|
self.move(frame.topLeft())
|
|
|
|
|
|
|
|
dark_palette = QPalette()
|
|
|
|
dark_palette.setColor(QPalette.Window, QColor(53, 53, 53))
|
|
|
|
dark_palette.setColor(QPalette.WindowText, Qt.white)
|
|
|
|
dark_palette.setColor(QPalette.Base, QColor(35, 35, 35))
|
|
|
|
dark_palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
|
|
|
|
dark_palette.setColor(QPalette.ToolTipBase, Qt.white)
|
|
|
|
dark_palette.setColor(QPalette.ToolTipText, Qt.white)
|
|
|
|
dark_palette.setColor(QPalette.Text, Qt.white)
|
|
|
|
dark_palette.setColor(QPalette.Button, QColor(53, 53, 53))
|
|
|
|
dark_palette.setColor(QPalette.ButtonText, Qt.white)
|
|
|
|
dark_palette.setColor(QPalette.BrightText, Qt.red)
|
|
|
|
dark_palette.setColor(QPalette.Highlight, QColor(142, 45, 197).lighter())
|
|
|
|
dark_palette.setColor(QPalette.HighlightedText, Qt.black)
|
|
|
|
self.setPalette(dark_palette)
|
|
|
|
self.setStyleSheet("""
|
|
|
|
QLineEdit, QTableWidget {
|
|
|
|
background: #2a2a2a;
|
|
|
|
border: 2px solid #444;
|
|
|
|
border-radius: 5px;
|
|
|
|
padding: 5px;
|
|
|
|
color: white;
|
|
|
|
}
|
|
|
|
QHeaderView::section {
|
|
|
|
background-color: #444;
|
|
|
|
color: white;
|
|
|
|
padding: 4px;
|
|
|
|
border: 1px solid #555;
|
|
|
|
}
|
|
|
|
QTableWidget::item {
|
|
|
|
padding: 5px;
|
|
|
|
}
|
|
|
|
QPushButton {
|
|
|
|
background: #444;
|
|
|
|
padding: 5px 10px;
|
|
|
|
border-radius: 4px;
|
|
|
|
color: white;
|
|
|
|
}
|
|
|
|
QPushButton:hover {
|
|
|
|
background: #555;
|
|
|
|
}
|
|
|
|
""")
|
|
|
|
|
|
|
|
central_widget = QWidget()
|
|
|
|
self.setCentralWidget(central_widget)
|
|
|
|
main_layout = QVBoxLayout(central_widget)
|
|
|
|
main_layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
main_layout.setSpacing(20)
|
|
|
|
|
|
|
|
scan_layout = QVBoxLayout()
|
|
|
|
scan_layout.setSpacing(15)
|
|
|
|
|
|
|
|
self.inputs_scan = CustomLineEdit()
|
|
|
|
self.inputs_scan.setFont(QFont('Consolas', 24))
|
|
|
|
self.inputs_scan.setAlignment(Qt.AlignCenter)
|
|
|
|
self.inputs_scan.setPlaceholderText('')
|
|
|
|
self.inputs_scan.setMaxLength(256)
|
|
|
|
self.inputs_scan.returnPressed.connect(self.on_scan)
|
|
|
|
|
|
|
|
self.labels_status = QLabel('')
|
|
|
|
self.labels_status.setFont(QFont('Microsoft YaHei', 24))
|
|
|
|
self.labels_status.setAlignment(Qt.AlignCenter)
|
|
|
|
self.labels_status.setAutoFillBackground(True)
|
|
|
|
self.set_status('wait', '')
|
|
|
|
|
|
|
|
scan_layout.addWidget(self.inputs_scan)
|
|
|
|
scan_layout.addWidget(self.labels_status)
|
|
|
|
|
|
|
|
self.labels_history = QLabel('扫描历史')
|
|
|
|
self.labels_history.setFont(QFont('Microsoft YaHei', 14))
|
|
|
|
self.labels_history.setStyleSheet('color: white;')
|
|
|
|
|
|
|
|
self.tables_history = QTableWidget()
|
|
|
|
self.tables_history.setColumnCount(4)
|
|
|
|
self.tables_history.setHorizontalHeaderLabels(['时间', '条码', '结果', '详情'])
|
|
|
|
self.tables_history.horizontalHeader().setStretchLastSection(True)
|
|
|
|
self.tables_history.verticalHeader().setVisible(False)
|
|
|
|
self.tables_history.setEditTriggers(QTableWidget.NoEditTriggers)
|
|
|
|
self.tables_history.setSelectionBehavior(QTableWidget.SelectRows)
|
|
|
|
self.tables_history.setFont(QFont('Consolas', 12))
|
|
|
|
self.tables_history.setColumnWidth(0, 200)
|
|
|
|
self.tables_history.setColumnWidth(1, 435)
|
|
|
|
self.tables_history.setColumnWidth(2, 80)
|
|
|
|
|
|
|
|
foot_layout = QHBoxLayout()
|
|
|
|
self.labels_operator = QLabel('')
|
|
|
|
self.labels_operator.setFont(QFont('Microsoft YaHei', 14))
|
|
|
|
self.labels_operator.setStyleSheet('color: white;')
|
|
|
|
|
|
|
|
self.button_change_operator = QPushButton('变更作业人员')
|
|
|
|
self.button_change_operator.setFont(QFont('Microsoft YaHei', 12))
|
|
|
|
self.button_change_operator.setStyleSheet('color: white;')
|
|
|
|
self.button_change_operator.clicked.connect(self.on_show_dialog_change_operator)
|
|
|
|
|
|
|
|
self.button_defect = QPushButton('不良发现扫描')
|
|
|
|
self.button_defect.setFont(QFont('Microsoft YaHei', 12))
|
|
|
|
self.button_defect.setStyleSheet('color: white;')
|
|
|
|
self.button_defect.clicked.connect(self.on_show_dialog_defect)
|
|
|
|
|
|
|
|
foot_layout.addWidget(self.labels_operator)
|
|
|
|
foot_layout.addStretch()
|
|
|
|
foot_layout.addWidget(self.button_defect)
|
|
|
|
foot_layout.addWidget(self.button_change_operator)
|
|
|
|
|
|
|
|
main_layout.addLayout(scan_layout)
|
|
|
|
main_layout.addWidget(self.labels_history)
|
|
|
|
main_layout.addWidget(self.tables_history)
|
|
|
|
main_layout.addLayout(foot_layout)
|
|
|
|
|
|
|
|
self.init()
|
|
|
|
self.show()
|
|
|
|
QTimer.singleShot(35, self.init_delay)
|
|
|
|
|
|
|
|
def init(self):
|
|
|
|
self.set_operators('Operator')
|
|
|
|
self.set_equipment('%s' % (platform.node(),))
|
|
|
|
|
|
|
|
def init_delay(self):
|
|
|
|
config = os.path.join(os.path.dirname(__file__), '%s.json' % (os.path.splitext(os.path.basename(__file__))[0],))
|
|
|
|
reader = SafeJSONConfigReader(config)
|
|
|
|
if (not reader.is_loaded_success()) == 1:
|
|
|
|
MessageBox.show_message('配置文件错误', '%s' % (config,), self)
|
|
|
|
self.exit()
|
|
|
|
return None
|
|
|
|
data = reader.get('window_title'), reader.get('server_host'), reader.get('server_port'), reader.get('station_id'), reader.get('scan_regexp')
|
|
|
|
|
|
|
|
if (not data[1] or not data[2] or not data[3]) == 1:
|
|
|
|
MessageBox.show_message('配置文件错误', '%s' % ('主机信息或工站配置错误',), self)
|
|
|
|
self.exit()
|
|
|
|
return None
|
|
|
|
try:
|
|
|
|
self.setWindowTitle('%s' % (data[0],))
|
|
|
|
self.host, self.port, self.station, self.scan_regexp = str(data[1]).strip(), int(data[2]), str(data[3]).strip(), data[4]
|
|
|
|
except (Exception,) as e:
|
|
|
|
MessageBox.show_message('错误', '%s' % (e,), self)
|
|
|
|
self.exit()
|
|
|
|
return None
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def exit():
|
|
|
|
QApplication.quit()
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def mes_upload(host: str, port: int, station_id: str, barcode: str, operators: str, equipment: str, result: int):
|
|
|
|
try:
|
|
|
|
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
client_socket.settimeout(3.0)
|
|
|
|
client_socket.connect((host, port))
|
|
|
|
client_socket.sendall(('100,%s,%s,%s,%s,%s,%s' % (station_id, barcode, result, operators, equipment, equipment)).encode('utf-8'))
|
|
|
|
received_data = str(client_socket.recv(1024).decode('GBK'))
|
|
|
|
client_socket.close()
|
|
|
|
if received_data.find(base64.b64decode(bytes('5p2h56CB5LiN5a2Y5Zyo', encoding='utf-8')).decode()) != -1:
|
|
|
|
return 2, '不存在的条码'
|
|
|
|
if received_data.find(base64.b64decode(bytes('5Lqn5ZOB5Z6L5Y+36ZSZ6K+v', encoding='utf-8')).decode()) != -1:
|
|
|
|
return 2, '产品型号错误'
|
|
|
|
if received_data.find(base64.b64decode(bytes('5bel5bqP5LqS5qOA5aSx6LSl', encoding='utf-8')).decode()) != -1:
|
|
|
|
return 2, '工序互检失败'
|
|
|
|
if received_data.find(base64.b64decode(bytes('6L+U5L+u5aSE55CG', encoding='utf-8')).decode()) != -1:
|
|
|
|
return 2, '条码已被锁定'
|
|
|
|
if received_data.find(base64.b64decode(bytes('5LiK5bel5bqPTkc=', encoding='utf-8')).decode()) != -1:
|
|
|
|
return 2, '上道工序不良'
|
|
|
|
if received_data.find(base64.b64decode(bytes('5rKh5pyJ5byC5bi4', encoding='utf-8')).decode()) != -1:
|
|
|
|
return 0, ''
|
|
|
|
print(received_data)
|
|
|
|
return 9, '未知错误信息'
|
|
|
|
except socket.timeout:
|
|
|
|
return 1, '网络连接超时'
|
|
|
|
|
|
|
|
def set_status(self, status=None, message=None):
|
|
|
|
palette = self.labels_status.palette()
|
|
|
|
if status == 'wait':
|
|
|
|
palette.setColor(QPalette.WindowText, Qt.white)
|
2025-04-12 09:48:26 +08:00
|
|
|
palette.setColor(QPalette.Window, QColor(80, 80, 80))
|
|
|
|
self.labels_status.setPalette(palette)
|
2025-04-11 19:23:11 +08:00
|
|
|
if status == 'pass':
|
2025-04-12 09:48:26 +08:00
|
|
|
palette.setColor(QPalette.WindowText, Qt.white)
|
|
|
|
palette.setColor(QPalette.Window, QColor(101, 200, 68))
|
|
|
|
self.labels_status.setPalette(palette)
|
|
|
|
QTimer.singleShot(500, lambda: [palette.setColor(QPalette.Window, QColor(80, 80, 80)), self.labels_status.setPalette(palette)][0])
|
2025-04-11 19:23:11 +08:00
|
|
|
self.inputs_scan.setStyleSheet("""
|
|
|
|
QLineEdit {
|
|
|
|
background: #2a2a2a;
|
|
|
|
border: 2px solid #444;
|
|
|
|
border-radius: 5px;
|
|
|
|
padding: 5px;
|
|
|
|
color: white;
|
|
|
|
}
|
|
|
|
""")
|
|
|
|
if status == 'fail':
|
|
|
|
palette.setColor(QPalette.WindowText, Qt.white)
|
2025-04-12 09:48:26 +08:00
|
|
|
palette.setColor(QPalette.Window, QColor(222, 61, 66))
|
|
|
|
self.labels_status.setPalette(palette)
|
2025-04-11 19:23:11 +08:00
|
|
|
self.inputs_scan.setStyleSheet('border: 2px solid red;')
|
|
|
|
self.labels_status.setText(message if message else '')
|
|
|
|
|
|
|
|
def set_operators(self, data):
|
|
|
|
self.operators = str(data).strip()
|
|
|
|
self.labels_operator.setText('作业人员: %s' % (self.operators,))
|
|
|
|
print('人员变更 => %s' % (self.operators,))
|
|
|
|
|
|
|
|
def set_equipment(self, data):
|
|
|
|
self.equipment = str(data).strip()
|
|
|
|
print('设备变更 => %s' % (self.equipment,))
|
|
|
|
|
|
|
|
def add_scan_record(self, timestr, timestamp, barcode, results, details):
|
|
|
|
max_records = 128
|
|
|
|
self.scanning_record.insert(0, [timestr, timestamp, barcode, results, details])
|
|
|
|
if (len(self.scanning_record) > max_records) == 1:
|
|
|
|
self.scanning_record = self.scanning_record[:max_records]
|
|
|
|
self.tables_history.setRowCount(len(self.scanning_record))
|
|
|
|
|
|
|
|
for row, (_timestr, _timestamp, _barcode, _results, _details) in enumerate(self.scanning_record):
|
|
|
|
timestr_item = QTableWidgetItem(_timestr)
|
|
|
|
timestr_item.setTextAlignment(Qt.AlignCenter | Qt.AlignVCenter)
|
|
|
|
self.tables_history.setItem(row, 0, timestr_item)
|
|
|
|
|
|
|
|
barcode_item = QTableWidgetItem(_barcode)
|
|
|
|
barcode_item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
|
|
self.tables_history.setItem(row, 1, barcode_item)
|
|
|
|
|
|
|
|
results_item = QTableWidgetItem(_results)
|
|
|
|
results_item.setTextAlignment(Qt.AlignCenter | Qt.AlignVCenter)
|
|
|
|
results_item.setForeground(QBrush(QColor(0, 255, 0))) if _results == 'PASS' else results_item.setForeground(QBrush(QColor(255, 0, 0)))
|
|
|
|
self.tables_history.setItem(row, 2, results_item)
|
|
|
|
|
|
|
|
details_item = QTableWidgetItem(_details)
|
|
|
|
details_item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
|
|
self.tables_history.setItem(row, 3, details_item)
|
|
|
|
|
|
|
|
def on_scan(self, barcode: str = '', result: int = 1):
|
|
|
|
edit = self.inputs_scan
|
|
|
|
text = str(barcode).strip() if barcode else edit.text().strip()
|
|
|
|
edit.clear() if barcode else None
|
|
|
|
if (result not in [1, 2]) == 1:
|
|
|
|
return None
|
|
|
|
if (text == '') == 1:
|
|
|
|
return None
|
|
|
|
if (len(text) > 128) == 1:
|
|
|
|
self.set_status('fail', '扫描内容过长')
|
|
|
|
edit.clear()
|
|
|
|
return None
|
|
|
|
if (self.scan_regexp and isinstance(self.scan_regexp, str) and not re.match(self.scan_regexp, text)) == 1:
|
|
|
|
self.set_status('fail', '规则验证失败')
|
|
|
|
edit.selectAll()
|
|
|
|
return None
|
|
|
|
if (self.scanning_latest and self.scanning_latest[1] == text and (time.time() < self.scanning_latest[0])) == 1:
|
|
|
|
self.scanning_latest[2] and self.set_status('fail', self.scanning_latest[2])
|
|
|
|
edit.selectAll()
|
|
|
|
return None
|
|
|
|
mes_upload_response = self.mes_upload(self.host, self.port, self.station, text, self.operators, self.equipment, result)
|
|
|
|
if (mes_upload_response[0] != 0) == 1:
|
|
|
|
self.scanning_latest = [time.time() + 1, text, '']
|
|
|
|
self.set_status('fail', mes_upload_response[1])
|
|
|
|
edit.selectAll()
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
self.scanning_latest = [time.time() + 5, text, '重复扫描']
|
|
|
|
self.set_status('pass', '上传成功')
|
|
|
|
self.add_scan_record(QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss'), int(time.time()), text, {1: 'PASS', 2: 'FAIL'}[result], '')
|
|
|
|
edit.clear()
|
|
|
|
return None
|
|
|
|
|
|
|
|
def on_show_dialog_change_operator(self):
|
|
|
|
dialog = TextInputDialog(title='变更作业人员', default_text=self.operators, regexp='^[0-9A-Za-z\\-]{6,12}$', placeholder='', parent=self)
|
|
|
|
if (dialog.exec_() == QDialog.Accepted) == 1:
|
|
|
|
operator = dialog.text()
|
|
|
|
operator and self.set_operators(operator)
|
|
|
|
self.inputs_scan.setFocus()
|
|
|
|
|
|
|
|
def on_show_dialog_defect(self):
|
|
|
|
dialog = TextInputDialog(title='不良发现扫描', default_text='', regexp='^.{1,128}$', placeholder='', parent=self)
|
|
|
|
if (dialog.exec_() == QDialog.Accepted) == 1:
|
|
|
|
products = dialog.text()
|
|
|
|
products and self.on_scan(products, 2)
|
|
|
|
self.inputs_scan.setFocus()
|
|
|
|
|
|
|
|
|
|
|
|
class MainRunner:
|
|
|
|
def __init__(self):
|
|
|
|
signal.signal(signal.SIGINT, self._handle_interrupt)
|
|
|
|
self.app_name = 'MesInterface'
|
|
|
|
self.app_version = '1.0.0.0'
|
|
|
|
self.app_publisher = 'zhaoyafan'
|
|
|
|
self.app_publisher_url = 'https://www.fanscloud.net/'
|
|
|
|
self.application = None
|
|
|
|
self.window = None
|
|
|
|
|
|
|
|
def _copy_files_and_directories(self, src, dst):
|
|
|
|
function_name = inspect.currentframe().f_code.co_name
|
|
|
|
if (os.path.exists(src)) != 1:
|
|
|
|
return None
|
|
|
|
if (os.path.isdir(src)) == 1:
|
|
|
|
if not os.path.exists(dst):
|
|
|
|
os.makedirs(dst)
|
|
|
|
for item in os.listdir(src):
|
|
|
|
s = os.path.join(src, item)
|
|
|
|
d = os.path.join(dst, item)
|
|
|
|
if os.path.isdir(s):
|
|
|
|
self.__getattribute__(function_name)(s, d)
|
|
|
|
else:
|
|
|
|
shutil.copy(s, d)
|
|
|
|
else:
|
|
|
|
shutil.copy(src, dst)
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
|
|
|
|
QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
|
|
|
|
self.application = QApplication(sys.argv)
|
|
|
|
self.window = MainWindow(app_name=self.app_name, app_version=self.app_version)
|
|
|
|
sys.exit(self.application.exec_())
|
|
|
|
|
|
|
|
def build(self):
|
|
|
|
if (str(__file__).endswith('.py')) == 0:
|
|
|
|
print('Build is not currently supported.', file=sys.stderr)
|
|
|
|
exit(1)
|
|
|
|
from_home = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
dist_home = os.path.join(os.path.dirname(__file__), '%s.dist' % (os.path.splitext(os.path.basename(__file__))[0],))
|
|
|
|
ask = input('%s %s: ' % ('Build?', '[Y/n]'))
|
|
|
|
if ask.lower().strip() == 'y':
|
|
|
|
subprocess.run(['pip', 'install', 'nuitka', '-q'], shell=True, env=os.environ)
|
|
|
|
subprocess.run([
|
|
|
|
'python',
|
|
|
|
'-m',
|
|
|
|
'nuitka',
|
|
|
|
'--standalone',
|
|
|
|
'--enable-plugin=pyqt5',
|
|
|
|
'--include-module=PyQt5',
|
|
|
|
'--windows-console-mode=disable',
|
|
|
|
'--windows-icon-from-ico=favicon.ico',
|
|
|
|
'--product-name=%s' % (self.app_name,),
|
|
|
|
'--file-description=%s' % (self.app_name,),
|
|
|
|
'--product-version=%s' % (self.app_version,),
|
|
|
|
'--copyright=Copyright (C) 2025',
|
|
|
|
'--output-dir=%s' % (os.path.join(os.path.dirname(__file__)),),
|
|
|
|
'%s' % (__file__,)
|
|
|
|
], shell=True, env=os.environ)
|
|
|
|
for i in ['PyQt5', 'favicon.ico', 'MesInterface.json']:
|
|
|
|
self._copy_files_and_directories('%s/%s' % (from_home, i), '%s/%s' % (dist_home, i))
|
|
|
|
else:
|
|
|
|
if (not os.path.exists(dist_home)) == 1:
|
|
|
|
return None
|
|
|
|
ask = input('%s %s: ' % ('Compile setup program?', '[Y/n]'))
|
|
|
|
if ask.lower().strip() == 'y':
|
|
|
|
compile_file = os.path.join(os.path.dirname(__file__), '%s.iss' % (os.path.splitext(os.path.basename(__file__))[0],))
|
|
|
|
compile_template = os.path.join(os.path.dirname(__file__), '%s.iss.template' % (os.path.splitext(os.path.basename(__file__))[0],))
|
|
|
|
compiler = 'C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe'
|
|
|
|
if (os.path.exists(compile_template)) != 1:
|
|
|
|
print('The template file \"%s\" does not exist.' % (compile_template,), file=sys.stderr)
|
|
|
|
return None
|
|
|
|
if (os.path.exists(compiler)) != 1:
|
|
|
|
print('The compiler \"%s\" does not exist. Please check if Inno Setup is installed. You can download it at https://www.innosetup.com/' % (compiler,),
|
|
|
|
file=sys.stderr)
|
|
|
|
return None
|
|
|
|
Path(compile_file).write_text(
|
|
|
|
Path(compile_template).read_text().replace(
|
|
|
|
'%APPNAME%',
|
|
|
|
self.app_name
|
|
|
|
).replace(
|
|
|
|
'%APPEXEC%',
|
|
|
|
os.path.splitext(os.path.basename(__file__))[0]
|
|
|
|
).replace(
|
|
|
|
'%APPVERSION%',
|
|
|
|
self.app_version
|
|
|
|
).replace(
|
|
|
|
'%APPBUILDDATE%',
|
|
|
|
time.strftime('%Y%m%d', time.localtime())
|
|
|
|
).replace(
|
|
|
|
'%APPPUBLISHER%',
|
|
|
|
self.app_publisher
|
|
|
|
).replace(
|
|
|
|
'%APPPUBLISHERURL%',
|
|
|
|
self.app_publisher_url
|
|
|
|
).replace(
|
|
|
|
'%DISABLEX64%',
|
|
|
|
'' if platform.architecture()[0] == '64bit' else '; '
|
|
|
|
)
|
|
|
|
)
|
|
|
|
subprocess.run([compiler, compile_file])
|
|
|
|
|
|
|
|
def _handle_interrupt(self, _signal, _frame):
|
|
|
|
print('Exit.', file=sys.stderr)
|
|
|
|
self.handle_interrupt()
|
|
|
|
|
|
|
|
def handle_interrupt(self):
|
|
|
|
try:
|
|
|
|
self.window.exit()
|
|
|
|
except Exception as e:
|
|
|
|
print(e, file=sys.stderr)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
if (os.path.basename(__file__).lower().endswith('.int')) == 1:
|
|
|
|
QCoreApplication.addLibraryPath(os.path.join(os.path.dirname(__file__), 'site-packages/PyQt5/Qt5/plugins'))
|
|
|
|
else:
|
|
|
|
QCoreApplication.addLibraryPath(os.path.join(os.path.dirname(__file__), 'PyQt5/Qt5/plugins'))
|
|
|
|
f_lock = open(file=os.path.join(tempfile.gettempdir(), '%s.lock' % hashlib.md5(bytes(__file__, encoding='utf-8')).hexdigest()[:16]), mode='w', encoding='utf-8')
|
|
|
|
if (not flock(f_lock, LOCK_EX | LOCK_NB)) == 1:
|
|
|
|
app = QApplication(sys.argv)
|
|
|
|
msg = QMessageBox()
|
|
|
|
msg.setIcon(QMessageBox.Warning)
|
|
|
|
msg.setText('The application is already running.')
|
|
|
|
msg.setWindowTitle('Warning')
|
|
|
|
msg.setStandardButtons(QMessageBox.Cancel)
|
|
|
|
msg.exec_()
|
|
|
|
app.exit(1)
|
|
|
|
sys.exit(1)
|
|
|
|
else:
|
|
|
|
MainRunner().run() if (len(sys.argv) > 1 and sys.argv[1] == '--build') == 0 else MainRunner().build()
|