From 0557ba7f1cfed2589a72d5f3c09010015ac4730b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=AF=E4=BD=B3?= <13101321+jfen5577@user.noreply.gitee.com> Date: Fri, 5 Sep 2025 15:40:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=88=86=E7=A6=BB=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E7=B1=BB=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QT5_Project/KD_ZM_8_XCF/APPWindow.py | 25 +++++-- QT5_Project/KD_ZM_8_XCF/UIFrameWork.py | 4 +- .../Shared_CODE/DialogFaceEnrollItgSingle.py | 65 +++++++++++++++++++ ...logFaceView.py => DialogFaceUserManage.py} | 48 ++------------ QT5_Project/Shared_CODE/DialogFaceVerify.py | 54 +++++++++++++++ .../Shared_CODE/FaceRecognitionProtocol.py | 58 +++++++++++------ 6 files changed, 182 insertions(+), 72 deletions(-) create mode 100644 QT5_Project/Shared_CODE/DialogFaceEnrollItgSingle.py rename QT5_Project/Shared_CODE/{DialogFaceView.py => DialogFaceUserManage.py} (73%) create mode 100644 QT5_Project/Shared_CODE/DialogFaceVerify.py diff --git a/QT5_Project/KD_ZM_8_XCF/APPWindow.py b/QT5_Project/KD_ZM_8_XCF/APPWindow.py index 5a4ed98..e051b56 100644 --- a/QT5_Project/KD_ZM_8_XCF/APPWindow.py +++ b/QT5_Project/KD_ZM_8_XCF/APPWindow.py @@ -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): diff --git a/QT5_Project/KD_ZM_8_XCF/UIFrameWork.py b/QT5_Project/KD_ZM_8_XCF/UIFrameWork.py index 3e98499..a2a8c2a 100644 --- a/QT5_Project/KD_ZM_8_XCF/UIFrameWork.py +++ b/QT5_Project/KD_ZM_8_XCF/UIFrameWork.py @@ -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 * diff --git a/QT5_Project/Shared_CODE/DialogFaceEnrollItgSingle.py b/QT5_Project/Shared_CODE/DialogFaceEnrollItgSingle.py new file mode 100644 index 0000000..c3b6d1e --- /dev/null +++ b/QT5_Project/Shared_CODE/DialogFaceEnrollItgSingle.py @@ -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) diff --git a/QT5_Project/Shared_CODE/DialogFaceView.py b/QT5_Project/Shared_CODE/DialogFaceUserManage.py similarity index 73% rename from QT5_Project/Shared_CODE/DialogFaceView.py rename to QT5_Project/Shared_CODE/DialogFaceUserManage.py index 50620d8..cdfe261 100644 --- a/QT5_Project/Shared_CODE/DialogFaceView.py +++ b/QT5_Project/Shared_CODE/DialogFaceUserManage.py @@ -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() diff --git a/QT5_Project/Shared_CODE/DialogFaceVerify.py b/QT5_Project/Shared_CODE/DialogFaceVerify.py new file mode 100644 index 0000000..f0d6e40 --- /dev/null +++ b/QT5_Project/Shared_CODE/DialogFaceVerify.py @@ -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) diff --git a/QT5_Project/Shared_CODE/FaceRecognitionProtocol.py b/QT5_Project/Shared_CODE/FaceRecognitionProtocol.py index f4534f3..22420fa 100644 --- a/QT5_Project/Shared_CODE/FaceRecognitionProtocol.py +++ b/QT5_Project/Shared_CODE/FaceRecognitionProtocol.py @@ -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"}