文件分离函数类,增加提示信息

This commit is contained in:
冯佳
2025-09-05 15:40:00 +08:00
parent 79f733126b
commit 0557ba7f1c
6 changed files with 182 additions and 72 deletions

View File

@ -1,7 +1,7 @@
# This Python file uses the following encoding: utf-8
import sys
import time
from PyQt5.QtWidgets import QWidget, QLineEdit
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtWidgets import QApplication, QMainWindow, QStackedWidget, QWidget, QLayout, QLabel, QLineEdit, QPushButton, QMessageBox, QShortcut, QDialog,QTextEdit
from PyQt5 import uic
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QRunnable, QMutex, QTimer, QEvent
@ -19,7 +19,9 @@ import uart_group_config as group_config
from mqtt_device import class_comm_mqtt_thread, class_comm_mqtt_interface
from print_color import *
from Shared_CODE.get_tip_prop import *
from Shared_CODE.DialogFaceView import *
from QT5_Project.Shared_CODE.DialogFaceVerify import VerifyDialog
from QT5_Project.Shared_CODE.DialogFaceEnrollItgSingle import EnrollItgSingleDialog
from QT5_Project.Shared_CODE.DialogFaceUserManage import UserManageDialog, save_user, load_users, save_users_list
# 设置 UI 目录的路径
ui_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'UI'))
@ -441,8 +443,6 @@ class QFaceCameraViewPage(PageTemplate):
#信号绑定
# self.btn_refresh.clicked.connect(self.refresh_ports)
# self.btn_conn.clicked.connect(self.toggle_conn)
self.btn_video.clicked.connect(self.toggle_video)
self.btn_video_mode.clicked.connect(self.toggle_video_mode)
self.chk_face_box.stateChanged.connect(self.toggle_face_box)
@ -468,7 +468,6 @@ class QFaceCameraViewPage(PageTemplate):
# 串口管理
def auto_connect_serial(self):
"""根据 config.py 选择默认串口,但不立即打开"""
ports = [p.device for p in serial.tools.list_ports.comports()]
if not ports:
self.log("[WARN] 未检测到任何串口设备")
@ -517,7 +516,6 @@ class QFaceCameraViewPage(PageTemplate):
except:
pass
self.ser = None
self.btn_conn.setText("连接")
self.log("[INFO] 串口已关闭")
def toggle_conn(self):
@ -528,7 +526,8 @@ class QFaceCameraViewPage(PageTemplate):
def send(self, frame: bytes):
if not self.ser or not getattr(self.ser, "is_open", False):
QMessageBox.warning(self, "提示", "请先连接串口")
inform_box : DialogInform = DialogInform()
inform_box.information("提示", "请先连接串口")
return
try:
self.ser.write(frame)
@ -563,6 +562,8 @@ class QFaceCameraViewPage(PageTemplate):
self.video_label.setPixmap(QPixmap())
self.btn_video.setText("打开视频")
self.log("[INFO] 视频已关闭")
inform_box : DialogInform = DialogInform()
inform_box.information("提示", "视频已关闭")
return
if self.video_label.width()<50 or self.video_label.height()<50:
@ -574,26 +575,36 @@ class QFaceCameraViewPage(PageTemplate):
self.video_worker.start()
self.btn_video.setText("关闭视频")
self.log("[INFO] 正在打开视频")
inform_box : DialogInform = DialogInform()
inform_box.information("提示", "正在打开视频")
def toggle_video_mode(self):
if self.current_video_mode==0:
self.send_uvc(0)
self.current_video_mode=1
self.log("[INFO] 已切换到红外视频模式")
inform_box : DialogInform = DialogInform()
inform_box.information("提示", "已切换到红外视频模式")
else:
self.send_uvc(1)
self.current_video_mode=0
self.log("[INFO] 已切换到彩色视频模式")
inform_box : DialogInform = DialogInform()
inform_box.information("提示", "已切换到彩色视频模式")
def toggle_face_box(self, state):
if state==Qt.Checked:
self.face_box_enabled=True
self.send_face_box(1)
self.log("[INFO] 人脸框已开启")
inform_box : DialogInform = DialogInform()
inform_box.information("提示", "人脸框已开启")
else:
self.face_box_enabled=False
self.send_face_box(0)
self.log("[INFO] 人脸框已关闭")
inform_box : DialogInform = DialogInform()
inform_box.information("提示", "人脸框已关闭")
# ---------------- 发送指令 ----------------
def send_uvc(self, mode):

View File

@ -20,7 +20,9 @@ from Shared_CODE.DialogModifyValue import DialogModifyValue
from Shared_CODE.DialogModifyAlias import DialogModifyAlias
from Shared_CODE.DialogModifyText import DialogModifyText
from Shared_CODE.DialogInform import DialogInform
from Shared_CODE.DialogFaceView import UserManageDialog, VerifyDialog
from QT5_Project.Shared_CODE.DialogFaceVerify import VerifyDialog
from QT5_Project.Shared_CODE.DialogFaceEnrollItgSingle import EnrollItgSingleDialog
from QT5_Project.Shared_CODE.DialogFaceUserManage import UserManageDialog
from Shared_CODE.get_tip_prop import *
from print_color import *

View File

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import sys, os, io, csv, time, datetime
from typing import Callable, Optional
# PyQt5
from PyQt5 import QtWidgets, uic
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QSize
from PyQt5.QtGui import QImage, QPixmap, QKeySequence
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QComboBox, QTextEdit, QFileDialog, QMessageBox,
QGroupBox, QGridLayout, QDialog, QFormLayout, QSpinBox, QCheckBox,
QLineEdit, QTableWidget, QTableWidgetItem,QDialogButtonBox,QShortcut
)
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import cv2
from PIL import Image
import serial
import serial.tools.list_ports
# ---------- 协议导入----------
from Shared_CODE.FaceRecognitionProtocol import (
build_reset, build_uvc_view, build_face_view, build_verify,
build_enroll_itg_single, build_delete_all, build_get_all_userid,build_delete_user,
MID_REPLY, MID_NOTE, CMD_ENROLL, CMD_ENROLL_ITG,
parse_reply, parse_note
)
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
ui_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../Shared_UI'))
enrill_ui_file_path = os.path.join(ui_path, "enroll.ui")
class EnrollItgSingleDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
uic.loadUi(enrill_ui_file_path, self)
self.cb_admin.setCurrentIndex(0)
self.btn_ok.clicked.connect(self.accept)
def values(self):
admin_val = self.cb_admin.currentIndex()
uname = self.le_name.text().strip()[:32]
face_dir = 0
if self.chk_mid.isChecked(): face_dir |= 0x01
if self.chk_right.isChecked(): face_dir |= 0x02
if self.chk_left.isChecked(): face_dir |= 0x04
if self.chk_down.isChecked(): face_dir |= 0x08
if self.chk_up.isChecked(): face_dir |= 0x10
if face_dir == 0: face_dir = 0x01
timeout_val = self.sb_timeout.value()
try:
itg_val = int(self.le_itg.text().strip(), 0)
except ValueError:
itg_val = 0
return admin_val, uname, face_dir, timeout_val, itg_val
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = EnrollItgSingleDialog()
dialog.exec()
sys.exit(0)

View File

@ -6,13 +6,14 @@ from typing import Callable, Optional
# PyQt5
from PyQt5 import QtWidgets, uic
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QSize
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtGui import QImage, QPixmap, QKeySequence
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QComboBox, QTextEdit, QFileDialog, QMessageBox,
QGroupBox, QGridLayout, QDialog, QFormLayout, QSpinBox, QCheckBox,
QLineEdit, QTableWidget, QTableWidgetItem
QLineEdit, QTableWidget, QTableWidgetItem,QDialogButtonBox,QShortcut
)
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import cv2
from PIL import Image
@ -30,8 +31,6 @@ from Shared_CODE.FaceRecognitionProtocol import (
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
ui_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../Shared_UI'))
users_ui_file_path = os.path.join(ui_path, "users.ui")
verify_ui_file_path = os.path.join(ui_path, "verify.ui")
enrill_ui_file_path = os.path.join(ui_path, "enroll.ui")
CSV_FILE = os.path.join(ui_path, "users.csv")
# -------------------- CSV 工具 --------------------"
@ -71,8 +70,9 @@ def save_user(user_id: int, user_name: str) -> bool:
class UserManageDialog(QDialog):
def __init__(self, parent, send_func):
super().__init__(parent)
uic.loadUi(users_ui_file_path, self)
self.send_func = send_func
uic.loadUi(users_ui_file_path, self)
self.btn_delete.clicked.connect(self.delete_selected)
self.btn_refresh.clicked.connect(self.refresh)
self.btn_get.clicked.connect(self.get_from_device)
@ -114,44 +114,6 @@ class UserManageDialog(QDialog):
self.refresh()
QMessageBox.information(self, "提示", "已请求删除所有用户并清空本地记录")
class VerifyDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
uic.loadUi(verify_ui_file_path, self)
self.cb_rightaway.setCurrentIndex(0)
self.sb_timeout.setValue(10)
self.btn_ok.clicked.connect(self.accept)
def values(self):
pd_val = self.cb_rightaway.currentIndex()
timeout_val = self.sb_timeout.value()
return pd_val, timeout_val
class EnrollItgSingleDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
uic.loadUi(enrill_ui_file_path, self)
self.cb_admin.setCurrentIndex(0)
self.btn_ok.clicked.connect(self.accept)
def values(self):
admin_val = self.cb_admin.currentIndex()
uname = self.le_name.text().strip()[:32]
face_dir = 0
if self.chk_mid.isChecked(): face_dir |= 0x01
if self.chk_right.isChecked(): face_dir |= 0x02
if self.chk_left.isChecked(): face_dir |= 0x04
if self.chk_down.isChecked(): face_dir |= 0x08
if self.chk_up.isChecked(): face_dir |= 0x10
if face_dir == 0: face_dir = 0x01
timeout_val = self.sb_timeout.value()
try:
itg_val = int(self.le_itg.text().strip(), 0)
except ValueError:
itg_val = 0
return admin_val, uname, face_dir, timeout_val, itg_val
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = UserManageDialog()

View File

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import sys, os, io, csv, time, datetime
from typing import Callable, Optional
# PyQt5
from PyQt5 import QtWidgets, uic
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QSize
from PyQt5.QtGui import QImage, QPixmap, QKeySequence
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QComboBox, QTextEdit, QFileDialog, QMessageBox,
QGroupBox, QGridLayout, QDialog, QFormLayout, QSpinBox, QCheckBox,
QLineEdit, QTableWidget, QTableWidgetItem,QDialogButtonBox,QShortcut
)
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import cv2
from PIL import Image
import serial
import serial.tools.list_ports
# ---------- 协议导入----------
from Shared_CODE.FaceRecognitionProtocol import (
build_reset, build_uvc_view, build_face_view, build_verify,
build_enroll_itg_single, build_delete_all, build_get_all_userid,build_delete_user,
MID_REPLY, MID_NOTE, CMD_ENROLL, CMD_ENROLL_ITG,
parse_reply, parse_note
)
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
ui_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../Shared_UI'))
verify_ui_file_path = os.path.join(ui_path, "verify.ui")
class VerifyDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
uic.loadUi(verify_ui_file_path, self)
self.cb_rightaway.setCurrentIndex(0)
self.sb_timeout.setValue(10)
self.btn_ok.clicked.connect(self.accept)
def values(self):
pd_val = self.cb_rightaway.currentIndex()
timeout_val = self.sb_timeout.value()
return pd_val, timeout_val
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = VerifyDialog()
dialog.exec()
sys.exit(0)

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import struct
from dataclasses import dataclass
from typing import Optional, Tuple, List
from QT5_Project.Shared_CODE.DialogInform import DialogInform
SYNC = b"\xEF\xAA"
@ -296,71 +297,86 @@ def build_enroll_with_photo_chunk(seq: int, chunk: bytes) -> bytes:
# ---- Parsers (Reply / Note / Image) ----
def parse_reply(data: bytes) -> dict:
if len(data) < 2:
return {"type":"REPLY","error":"short"}
return {"type": "REPLY", "error": "short"}
mid = data[0]
result = data[1]
rest = data[2:]
# 初始化 info 字典
# 通用字段
info = {
"type":"REPLY",
"type": "REPLY",
"mid": mid,
"mid_name": CMD_NAMES.get(mid, f"0x{mid:02X}"),
"result": result,
"result_name": RESULT_NAMES.get(result, f"0x{result:02X}")
"result_name": RESULT_NAMES.get(result, f"0x{result:02X}"),
"ok": (result == 0), # 成功标志
}
# ========== 分支解析 ==========
if mid == CMD_VERIFY and len(rest) >= 36:
uid = (rest[0]<<8)|rest[1]
uid = (rest[0] << 8) | rest[1]
name = rest[2:34].rstrip(b"\x00").decode("utf-8", errors="ignore")
admin = rest[34]
unlock = rest[35]
info.update({"user_id": uid, "user_name": name, "admin": admin, "unlock_status": unlock})
info.update({
"user_id": uid,
"user_name": name,
"admin": admin,
"unlock_status": unlock,
})
elif mid in (CMD_ENROLL, 0x1D, CMD_ENROLL_ITG) and len(rest) >= 3:
uid = (rest[0]<<8)|rest[1]
uid = (rest[0] << 8) | rest[1]
face_dir = rest[2]
info.update({"user_id": uid, "face_direction": face_dir})
elif mid == CMD_GET_STATUS and len(rest) >= 1:
status = rest[0]
info.update({"status": status, "status_name": {
0: "空闲",
1: "录入中",
2: "验证中"
}.get(status, f"0x{status:02X}")})
status_map = {0: "空闲", 1: "录入中", 2: "验证中"}
info.update({"status": status, "status_name": status_map.get(status, f"0x{status:02X}")})
elif mid == CMD_GET_USER_INFO and len(rest) >= 35:
uid = (rest[0]<<8)|rest[1]
uid = (rest[0] << 8) | rest[1]
name = rest[2:34].decode("ascii", errors="ignore")
admin = rest[34]
info.update({"user_id": uid, "user_name": name, "admin": admin})
elif mid == CMD_GET_ALL_USERID and len(rest) >= 1:
n = rest[0]
ids = [(rest[i]<<8)|rest[i+1] for i in range(1, 1+2*n, 2) if i+1 < len(rest)]
ids = [(rest[i] << 8) | rest[i + 1] for i in range(1, 1 + 2 * n, 2) if i + 1 < len(rest)]
info.update({"count": n, "user_ids": ids})
elif mid == CMD_GET_VERSION:
info["version_str"] = rest.decode("ascii", errors="ignore")
elif mid == CMD_LED_CONTROL and len(rest) >= 1:
led_state = rest[0]
info.update({"led_state": led_state, "led_state_name": {
0: "",
1: ""
}.get(led_state, f"0x{led_state:02X}")})
led_map = {0: "", 1: ""}
info.update({"led_state": led_state, "led_state_name": led_map.get(led_state, f"0x{led_state:02X}")})
elif mid == CMD_ENROLL_WITH_PHOTO:
if len(rest) >= 2:
seq = (rest[0]<<8)|rest[1]
seq = (rest[0] << 8) | rest[1]
info["seq"] = seq
if len(rest) >= 6:
uid = (rest[2]<<8)|rest[3]
uid = (rest[2] << 8) | rest[3]
info["user_id"] = uid
# 自动生成提示消息
if info.get("ok"):
info["message"] = f"{info['mid_name']} 成功"
inform_box : DialogInform = DialogInform()
inform_box.information("提示", f"{info['mid_name']} 成功")
else:
info["message"] = f"{info['mid_name']} 失败: {info['result_name']}"
inform_box : DialogInform = DialogInform()
inform_box.information("提示", f"{info['mid_name']} 失败: {info['result_name']}")
return info
def parse_note(data: bytes) -> dict:
if len(data) < 1:
return {"type":"NOTE","error":"short"}