Compare commits
2 Commits
f12a289c57
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7205a21a8d | |||
| babab70845 |
@ -1,7 +1,7 @@
|
||||
# This Python file uses the following encoding: utf-8
|
||||
import sys
|
||||
import time
|
||||
from PyQt5.QtWidgets import QFileDialog
|
||||
from PyQt5.QtWidgets import QFileDialog,QHeaderView
|
||||
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
|
||||
@ -453,10 +453,21 @@ class QFaceCameraViewPage(PageTemplate):
|
||||
self.t_video.start()
|
||||
|
||||
self.face_verify_result = None # 用于存储人脸验证结果
|
||||
self.verify_in_progress = False # 标志当前是否有验证在进行中
|
||||
|
||||
self.auto_connect_serial()
|
||||
# 串口管理
|
||||
|
||||
self.refresh()
|
||||
|
||||
header = self.table.horizontalHeader()
|
||||
# 用户ID列固定宽度
|
||||
self.table.setColumnWidth(0, 100)
|
||||
# 用户名列自适应内容
|
||||
header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
|
||||
# 注册时间列填充剩余空间
|
||||
header.setSectionResizeMode(2, QHeaderView.Stretch)
|
||||
|
||||
# 串口管理
|
||||
def auto_connect_serial(self):
|
||||
ports = [p.device for p in serial.tools.list_ports.comports()]
|
||||
if not ports:
|
||||
@ -531,36 +542,104 @@ class QFaceCameraViewPage(PageTemplate):
|
||||
self.log("[WARN] 摄像头线程已停止")
|
||||
self.video_worker=None # 自动重连可在这里实现
|
||||
|
||||
# 帧处理
|
||||
def on_frame(self, fr: dict):
|
||||
self.log("< " + fr["raw"].hex(" "))
|
||||
"""
|
||||
接收到一帧数据后的处理
|
||||
"""
|
||||
raw_bytes = fr.get("raw", b"")
|
||||
self.log("< " + raw_bytes.hex(" "))
|
||||
|
||||
msg_id = fr.get("msg_id")
|
||||
data = fr.get("data", b"")
|
||||
|
||||
if msg_id == MID_REPLY:
|
||||
info = parse_reply(data)
|
||||
self.log(f"[REPLY] {info}")
|
||||
if info.get("mid") in (CMD_ENROLL, CMD_ENROLL_ITG) and info.get("result") == 0x00:
|
||||
user_id = info.get("user_id")
|
||||
# 如果用户名为空,使用用户ID作为用户名
|
||||
user_name = self.last_enroll_name if self.last_enroll_name else str(user_id)
|
||||
if user_id: # 只需检查user_id存在即可,因为user_name已确保有值
|
||||
if save_user(user_id, user_name):
|
||||
self.log(f"[INFO] 用户 {user_name}(ID={user_id}) 已保存")
|
||||
else:
|
||||
QMessageBox.warning(self, "提示", f"用户ID {user_id} 已存在!")
|
||||
elif info.get("mid") == CMD_VERIFY :
|
||||
|
||||
if info.get("result") == 0x00:
|
||||
user_id = info.get("user_id")
|
||||
print(f"[INFO] 用户(ID={user_id}) 验证通过")
|
||||
self.face_verify_result = True
|
||||
else:
|
||||
self.face_verify_result = False
|
||||
mid = info.get("mid")
|
||||
result = info.get("result")
|
||||
|
||||
# 命令分发字典
|
||||
dispatch_reply = {
|
||||
CMD_ENROLL_SINGLE: self._handle_enroll_reply,
|
||||
CMD_VERIFY: self._handle_verify_reply,
|
||||
CMD_GET_ALL_USERID :self._handle_get_all_userid_reply,
|
||||
}
|
||||
|
||||
handler = dispatch_reply.get(mid)
|
||||
if handler:
|
||||
handler(info)
|
||||
|
||||
elif msg_id == MID_NOTE:
|
||||
info = parse_note(data)
|
||||
self.log(f"[NOTE] {info}")
|
||||
self._handle_note(info)
|
||||
|
||||
# ---------------- NOTE 处理 ----------------
|
||||
|
||||
def _handle_note(self, info: dict):
|
||||
"""
|
||||
处理 NOTE 消息,给用户交互提示
|
||||
"""
|
||||
user_message = info.get("user_message")
|
||||
nid = info.get("nid")
|
||||
|
||||
# 仅在验证进行中时提示 NOTE
|
||||
if getattr(self, "verify_in_progress", False):
|
||||
if user_message:
|
||||
DialogInform(self).information("提示", user_message)
|
||||
|
||||
# 日志记录(工程用)
|
||||
if nid == NID_FACE_STATE:
|
||||
state = info.get("state")
|
||||
self.log(f"[INFO] 人脸状态: {state}, yaw={info.get('yaw')}, pitch={info.get('pitch')}, roll={info.get('roll')}")
|
||||
elif nid == NID_READY:
|
||||
self.log("[INFO] 模组已就绪")
|
||||
elif nid == NID_OTA_DONE:
|
||||
self.log("[INFO] 固件升级完成")
|
||||
elif nid == NID_UNKNOWNERROR:
|
||||
self.log("[ERROR] 模组发生未知错误")
|
||||
|
||||
# ---------------- REPLY 处理 ----------------
|
||||
|
||||
def _handle_enroll_reply(self, info: dict):
|
||||
user_id = info.get("user_id")
|
||||
if info.get("result") == 0x00:
|
||||
if user_id:
|
||||
if save_user(user_id):
|
||||
self.refresh()
|
||||
self.log(f"[INFO] 用户ID={user_id} 已保存")
|
||||
else:
|
||||
DialogInform(self).information("提示", f"用户ID {user_id} 已存在!")
|
||||
else:
|
||||
self.log(f"[ERROR] 注册失败, 用户ID={user_id}")
|
||||
|
||||
def _handle_verify_reply(self, info: dict):
|
||||
"""
|
||||
验证结果处理
|
||||
"""
|
||||
user_id = info.get("user_id")
|
||||
result = info.get("result")
|
||||
|
||||
# 验证结束,状态机复位
|
||||
self.verify_in_progress = False
|
||||
|
||||
if result == 0x00:
|
||||
print(f"[INFO] 用户(ID={user_id}) 验证通过")
|
||||
self.face_verify_result = True
|
||||
else:
|
||||
print(f"[INFO] 用户(ID={user_id}) 验证失败")
|
||||
self.face_verify_result = False
|
||||
|
||||
def _handle_get_all_userid_reply(self, info: dict):
|
||||
count = info.get("count")
|
||||
result = info.get("result")
|
||||
user_ids = info.get("user_ids", [])
|
||||
if result == 0x00:
|
||||
DialogInform(self).information("提示", f"{count} 个用户\n用户ID 列表:" + ", ".join(map(str, user_ids)))
|
||||
|
||||
|
||||
|
||||
|
||||
# 日志
|
||||
def log(self, s: str):
|
||||
@ -585,8 +664,6 @@ class QFaceCameraViewPage(PageTemplate):
|
||||
self.close_serial()
|
||||
super().closeEvent(e)
|
||||
|
||||
|
||||
|
||||
#P06故障查询页面 QFaultQueryPage
|
||||
class QFaultQueryPage(PageTemplate):
|
||||
def __init__(self, parent_window):
|
||||
@ -762,6 +839,8 @@ class APPWindow(QMainWindow):
|
||||
# self.setGeometry(0, 0, 1024, 768)
|
||||
# self.stack.setGeometry(0, 0, 1024, 768)
|
||||
|
||||
QFaceCameraViewPage.video_worker = None
|
||||
QFaceCameraViewPage.ser = None
|
||||
|
||||
self.showFullScreen()
|
||||
|
||||
@ -875,10 +954,10 @@ class APPWindow(QMainWindow):
|
||||
for camera_thread in self.camera_thread_list:
|
||||
thread_to_stop : CameraThread = camera_thread
|
||||
|
||||
if self.video_worker and self.video_worker.isRunning():
|
||||
self.video_worker.stop()
|
||||
self.video_worker.wait(300)
|
||||
if self.ser and getattr(self.ser,"is_open",False):
|
||||
if QFaceCameraViewPage.video_worker and QFaceCameraViewPage.video_worker.isRunning():
|
||||
QFaceCameraViewPage.video_worker.stop()
|
||||
QFaceCameraViewPage.video_worker.wait(300)
|
||||
if QFaceCameraViewPage.ser and getattr(QFaceCameraViewPage.ser,"is_open",False):
|
||||
self.close_serial()
|
||||
if thread_to_stop != None :
|
||||
thread_to_stop.close()
|
||||
|
||||
@ -3037,7 +3037,7 @@ color: rgb(255, 170, 0);</string>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Index=7, Action=SetPage5_1,SelectImag=IMxx_00D.png</string>
|
||||
<string>Index=7, Action=SetPage5_1,SelectImag=IMxx_00D.png,password</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
|
||||
@ -93,7 +93,7 @@ color: rgb(207, 0, 13);</string>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>SwitchOn,Index=2,Action=CmdExecute, SelectImag=SR_001.png, groupstart=6</string>
|
||||
<string>SwitchOn,Index=2,Action=CmdExecute, SelectImag=SR_001.png, password,groupstart=6</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
@ -115,7 +115,7 @@ color: rgb(207, 0, 13);</string>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>SwitchOff,Index=3,Action=CmdExecute, SelectImag=SR_001.png</string>
|
||||
<string>SwitchOff,Index=3,Action=CmdExecute, password,SelectImag=SR_001.png</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
@ -218,7 +218,7 @@ color: rgb(207, 0, 13);</string>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>SwitchOff,Index=4,Action=CmdExecute, SelectImag=SR_001.png</string>
|
||||
<string>SwitchOff,Index=4,Action=CmdExecute, password,SelectImag=SR_001.png</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
|
||||
@ -91,39 +91,13 @@
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QPushButton" name="btn_save">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>211</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background-color: rgb(0, 0, 0);
|
||||
color: rgb(170, 0, 0);</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>日志</string>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>72</width>
|
||||
<height>144</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTextEdit" name="txt_log">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>-20</x>
|
||||
<y>60</y>
|
||||
<y>20</y>
|
||||
<width>701</width>
|
||||
<height>301</height>
|
||||
<height>341</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
@ -182,7 +156,7 @@ color: rgb(170, 0, 0);</string>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string/>
|
||||
<string>mian=51</string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>命令</string>
|
||||
@ -197,7 +171,7 @@ color: rgb(170, 0, 0);</string>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Index=0, Action=Reset,SelectImag=IMxx_00F.png</string>
|
||||
<string>Index=0, Action=Reset,SelectImag=IMxx_00F.png,groupstart=8</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>复位</string>
|
||||
@ -213,65 +187,33 @@ color: rgb(170, 0, 0);</string>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Index=0, Action=VideoMode,SelectImag=IMxx_00F.png</string>
|
||||
<string>Index=1, Action=VideoMode,SelectImag=IMxx_00F.png</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>视频模式</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btn_verify">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>12</x>
|
||||
<y>115</y>
|
||||
<width>80</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Index=0, Action=Verify,password,SelectImag=IMxx_00F.png</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>识别</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btn_enroll">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>12</x>
|
||||
<y>144</y>
|
||||
<x>10</x>
|
||||
<y>210</y>
|
||||
<width>80</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Index=0, Action=EnrollItgSingle,SelectImag=IMxx_00F.png</string>
|
||||
<string>Index=5, Action=EnrollItgSingle,SelectImag=IMxx_00F.png</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>ITG注册</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btn_users">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>12</x>
|
||||
<y>173</y>
|
||||
<width>80</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Index=0, Action=Users,SelectImag=IMxx_00F.png</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>用户管理</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="FaceRecogTimeoutEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>310</y>
|
||||
<x>-10</x>
|
||||
<y>180</y>
|
||||
<width>120</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
@ -286,7 +228,7 @@ color: rgb(170, 0, 0);</string>
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>System=FaceRecogTimeout, Action=ModifySystem, Index=13,SelectImag=P4_ParaSelect.png, password</string>
|
||||
<string>System=FaceRecogTimeout, Action=ModifySystem, Index=4,SelectImag=P4_ParaSelect.png, password</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background-color: rgba(85, 170, 127,0);
|
||||
@ -306,13 +248,13 @@ color: rgb(177, 229, 252);</string>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>210</y>
|
||||
<y>90</y>
|
||||
<width>80</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Index=0, Action=ConnectCamera,password,SelectImag=IMxx_00F.png</string>
|
||||
<string>Index=2, Action=ConnectCamera,SelectImag=IMxx_00F.png</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>打开视频</string>
|
||||
@ -322,18 +264,50 @@ color: rgb(177, 229, 252);</string>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>90</y>
|
||||
<y>120</y>
|
||||
<width>80</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Index=0, Action=FaceBox,SelectImag=IMxx_00F.png</string>
|
||||
<string>Index=3, Action=FaceBox,SelectImag=IMxx_00F.png</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>人脸框</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btn_delete_user_id">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>240</y>
|
||||
<width>80</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Index=6, Action=DeleteUser,SelectImag=IMxx_00F.png</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>删除用户</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btn_delete_user_id_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>270</y>
|
||||
<width>80</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Index=7, Action=UserCount,SelectImag=IMxx_00F.png,groupend=8</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>查看用户</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QLabel" name="P05_01BG">
|
||||
<property name="geometry">
|
||||
@ -351,11 +325,40 @@ color: rgb(177, 229, 252);</string>
|
||||
<pixmap>../image/FaceCameraView.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTableWidget" name="table">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>1080</x>
|
||||
<y>520</y>
|
||||
<width>821</width>
|
||||
<height>371</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background-color: rgb(255, 255, 255);</string>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>用户ID</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>用户名</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>注册时间</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
<zorder>P05_01BG</zorder>
|
||||
<zorder>groupBox_sys</zorder>
|
||||
<zorder>group_log</zorder>
|
||||
<zorder>group_video</zorder>
|
||||
<zorder>group_cmd</zorder>
|
||||
<zorder>table</zorder>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import csv
|
||||
import inspect
|
||||
import cv2
|
||||
import json
|
||||
@ -10,7 +11,7 @@ from PyQt5 import uic
|
||||
from PyQt5.QtGui import QImage, QPixmap, QColor,QBrush, QKeySequence, QIcon, QPalette
|
||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QRunnable, QMutex, QTimer, QEvent, QSize, QDateTime
|
||||
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QFrame, QWidget, QLayout, QLabel
|
||||
from PyQt5.QtWidgets import QLineEdit, QPushButton, QMessageBox, QShortcut, QDialog
|
||||
from PyQt5.QtWidgets import QLineEdit, QPushButton, QMessageBox, QShortcut, QDialog,QTableWidgetItem
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from Shared_CODE.CameraThread import CameraThread
|
||||
import menu_utils as utils
|
||||
@ -22,7 +23,7 @@ from Shared_CODE.DialogModifyText import DialogModifyText
|
||||
from Shared_CODE.DialogInform import DialogInform
|
||||
from QT5_Project.Shared_CODE.FaceRecognitionProtocol import parse_reply
|
||||
from QT5_Project.Shared_CODE.DialogFaceEnrollItgSingle import EnrollItgSingleDialog
|
||||
from QT5_Project.Shared_CODE.DialogFaceUserManage import UserManageDialog
|
||||
from QT5_Project.Shared_CODE.DialogFaceUserManage import UserManageDialog, load_users, save_user, save_users_list, CSV_FILE
|
||||
|
||||
from Shared_CODE.get_tip_prop import *
|
||||
from print_color import *
|
||||
@ -41,7 +42,7 @@ 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, unpack_frame, parse_reply, parse_note,
|
||||
build_delete_user, unpack_frame, parse_reply, parse_note,build_enroll_single,
|
||||
MID_REPLY, MID_NOTE, CMD_ENROLL, CMD_ENROLL_ITG
|
||||
)
|
||||
|
||||
@ -801,6 +802,8 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface):
|
||||
print("密码认证成功")
|
||||
|
||||
elif choice == "face":
|
||||
# 初始化锁标志
|
||||
self._face_verify_locked = True # 开始验证时锁定
|
||||
# 人脸认证异步处理
|
||||
face_frame = self.parent_window.P05_01_FaceCameraView
|
||||
face_frame.face_verify_result = None # 重置标志位
|
||||
@ -809,6 +812,12 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface):
|
||||
timeout = system_parameter().get_verify_timeout()
|
||||
start_time = time.time()
|
||||
|
||||
# 停止并删除旧定时器
|
||||
if hasattr(self, "_face_verify_timer"):
|
||||
if self._face_verify_timer.isActive():
|
||||
self._face_verify_timer.stop()
|
||||
self._face_verify_timer.deleteLater()
|
||||
|
||||
# 创建定时器轮询标志位
|
||||
self._face_verify_timer = QTimer(self)
|
||||
self._face_verify_timer.setInterval(50) # 每50ms检查一次
|
||||
@ -817,10 +826,9 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface):
|
||||
nonlocal input
|
||||
if face_frame.face_verify_result is not None:
|
||||
self._face_verify_timer.stop()
|
||||
self._face_verify_locked = False # 解锁
|
||||
if face_frame.face_verify_result:
|
||||
input = True
|
||||
inform_box = DialogInform()
|
||||
inform_box.information("提示", "人脸认证成功")
|
||||
self._after_face_verify(select_object, action_str)
|
||||
else:
|
||||
input = False
|
||||
@ -828,10 +836,13 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface):
|
||||
inform_box.information("提示", "人脸认证失败")
|
||||
elif time.time() - start_time > timeout:
|
||||
self._face_verify_timer.stop()
|
||||
self._face_verify_locked = False # 解锁
|
||||
input = False
|
||||
inform_box = DialogInform()
|
||||
inform_box.information("提示", "人脸认证超时")
|
||||
|
||||
# 解绑旧槽,绑定新槽
|
||||
try:
|
||||
self._face_verify_timer.timeout.disconnect()
|
||||
except Exception:
|
||||
pass
|
||||
self._face_verify_timer.timeout.connect(check_face_result)
|
||||
self._face_verify_timer.start()
|
||||
return # 异步处理,直接返回
|
||||
@ -875,6 +886,11 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface):
|
||||
|
||||
elif "FaceBox" in action_str:
|
||||
self.toggle_face_box()
|
||||
|
||||
elif "DeleteUser" in action_str:
|
||||
self.delete_user_by_id()
|
||||
elif "UserCount" in action_str:
|
||||
self.query_user_count()
|
||||
|
||||
self.virtual_widget_action_process(select_object, action_str)
|
||||
|
||||
@ -919,6 +935,12 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface):
|
||||
elif "FaceBox" in action_str:
|
||||
self.toggle_face_box()
|
||||
|
||||
elif "DeleteUser" in action_str:
|
||||
self.delete_user_by_id()
|
||||
|
||||
elif "UserCount" in action_str:
|
||||
self.query_user_count()
|
||||
|
||||
self.virtual_widget_action_process(select_object, action_str)
|
||||
|
||||
def search_menu_match_object(self, object) :
|
||||
@ -936,16 +958,18 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface):
|
||||
def do_verify(self):
|
||||
pd_val = 0
|
||||
timeout_val = system_parameter().get_verify_timeout()
|
||||
self.send(build_verify(pd_val, timeout_val))
|
||||
face_send = self.parent_window.P05_01_FaceCameraView
|
||||
face_send.verify_in_progress = True
|
||||
face_send.send(build_verify(pd_val, timeout_val))
|
||||
|
||||
|
||||
def do_enroll_itg_single(self):
|
||||
admin_val = 0
|
||||
uname = " "
|
||||
face_dir = 31
|
||||
timeout_val = system_parameter().get_verify_timeout()
|
||||
itg_val = 0
|
||||
self.send(build_enroll_itg_single(admin_val, uname, face_dir, timeout_val, itg_val))
|
||||
face_send = self.parent_window.P05_01_FaceCameraView
|
||||
face_send.verify_in_progress = True
|
||||
self.send(build_enroll_single(admin_val, uname, timeout_val))
|
||||
|
||||
def do_manage_users(self):
|
||||
UserManageDialog(self, self.send).exec_()
|
||||
@ -1019,6 +1043,74 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface):
|
||||
else:
|
||||
self.log("[WARN] 串口未连接,无法控制人脸框")
|
||||
|
||||
def delete_user_by_id(self, CSV_FILE=CSV_FILE):
|
||||
users = load_users()
|
||||
|
||||
# 弹出对话框选择用户ID
|
||||
dialog_modify_text = DialogModifyValue(self)
|
||||
caption_str = "选择用户ID删除"
|
||||
dialog_modify_text.update_modify_info("", 0, caption_str)
|
||||
|
||||
if dialog_modify_text.exec() != QDialog.Accepted:
|
||||
return
|
||||
|
||||
# 获取用户输入的ID并转换为整数
|
||||
try:
|
||||
user_id = int(dialog_modify_text.value)
|
||||
except ValueError:
|
||||
DialogInform(self).information("提示", "请输入有效数字ID")
|
||||
return
|
||||
|
||||
user_id_str = str(user_id)
|
||||
|
||||
# 查找用户(用字符串匹配,避免类型问题)
|
||||
user = next((u for u in users if str(u["user_id"]).strip() == user_id_str), None)
|
||||
if not user:
|
||||
DialogInform(self).information("提示", "用户不存在")
|
||||
return
|
||||
|
||||
try:
|
||||
# 1️⃣ 下发删除命令(串口模块)
|
||||
try:
|
||||
self.send(build_delete_user(user_id))
|
||||
except Exception as e:
|
||||
DialogInform(self).information("提示", f"警告:串口删除失败,但本地仍会删除\n{e}")
|
||||
|
||||
# 2️⃣ 删除 CSV 文件对应行
|
||||
if os.path.exists(CSV_FILE):
|
||||
with open(CSV_FILE, "r", encoding="utf-8", newline="") as f:
|
||||
reader = csv.DictReader(f)
|
||||
fieldnames = reader.fieldnames
|
||||
new_rows = [row for row in reader if str(row.get("user_id")).strip() != user_id_str]
|
||||
|
||||
with open(CSV_FILE, "w", encoding="utf-8", newline="") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
writer.writerows(new_rows)
|
||||
|
||||
except Exception as e:
|
||||
DialogInform(self).information("提示", f"删除失败: {e}")
|
||||
return
|
||||
|
||||
# 3️⃣ 更新内存用户列表
|
||||
users = [u for u in users if str(u["user_id"]).strip() != user_id_str]
|
||||
save_users_list(users)
|
||||
|
||||
# 4️⃣ 提示删除成功
|
||||
DialogInform(self).information("提示", f"用户 {user.get('user_name', '')} (ID={user_id}) 已删除")
|
||||
self.refresh()
|
||||
|
||||
def query_user_count(self):
|
||||
self.send(build_get_all_userid())
|
||||
|
||||
def refresh(self):
|
||||
users = load_users()
|
||||
self.table.setRowCount(len(users))
|
||||
for r, u in enumerate(users):
|
||||
self.table.setItem(r, 0, QTableWidgetItem(str(u.get("user_id", ""))))
|
||||
self.table.setItem(r, 1, QTableWidgetItem(u.get("user_name", "")))
|
||||
self.table.setItem(r, 2, QTableWidgetItem(u.get("created_at", "")))
|
||||
|
||||
|
||||
################################################################################
|
||||
#刷新屏幕上的系统信息, 例如时间日期之类
|
||||
|
||||
@ -57,24 +57,23 @@ def save_users_list(users):
|
||||
w.writerow([u.get("user_id", ""), u.get("user_name", ""), u.get("created_at", "")])
|
||||
|
||||
|
||||
def save_user(user_id: int, user_name: str) -> bool:
|
||||
def save_user(user_id: int) -> bool:
|
||||
users = load_users()
|
||||
for u in users:
|
||||
if str(u["user_id"]) == str(user_id):
|
||||
return False
|
||||
created_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
users.append({"user_id": str(user_id), "user_name": user_name, "created_at": created_at})
|
||||
users.append({"user_id": str(user_id), "user_name": str(user_id), "created_at": created_at})
|
||||
save_users_list(users)
|
||||
return True
|
||||
|
||||
|
||||
class UserManageDialog(QDialog):
|
||||
def __init__(self, parent=None, send_func=None):
|
||||
super().__init__(parent)
|
||||
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)
|
||||
self.btn_del_all.clicked.connect(self.delete_all_users)
|
||||
self.refresh()
|
||||
@ -105,31 +104,15 @@ class UserManageDialog(QDialog):
|
||||
QShortcut(QKeySequence(Qt.Key_Home), self, activated=self.key_escape_process)
|
||||
|
||||
QShortcut(QKeySequence(Qt.Key_Return), self, activated=self.key_enter_process) # 普通回车
|
||||
|
||||
|
||||
def refresh(self):
|
||||
users = load_users()
|
||||
self.table.setRowCount(len(users))
|
||||
for r, u in enumerate(users):
|
||||
self.table.setItem(r, 0, QTableWidgetItem(str(u.get("user_id", ""))))
|
||||
self.table.setItem(r, 1, QTableWidgetItem(u.get("user_name", "")))
|
||||
self.table.setItem(r, 2, QTableWidgetItem(u.get("created_at", "")))
|
||||
users = load_users()
|
||||
self.table.setRowCount(len(users))
|
||||
for r, u in enumerate(users):
|
||||
self.table.setItem(r, 0, QTableWidgetItem(str(u.get("user_id", ""))))
|
||||
self.table.setItem(r, 1, QTableWidgetItem(u.get("user_name", "")))
|
||||
self.table.setItem(r, 2, QTableWidgetItem(u.get("created_at", "")))
|
||||
|
||||
def delete_selected(self):
|
||||
row = self.table.currentRow()
|
||||
if row < 0:
|
||||
QMessageBox.warning(self, "提示", "删除选择用户")
|
||||
return
|
||||
uid = self.table.item(row, 0).text()
|
||||
uname = self.table.item(row, 1).text()
|
||||
try:
|
||||
uid_int = int(uid)
|
||||
self.send_func(build_delete_user(uid_int))
|
||||
except:
|
||||
pass
|
||||
users = [u for u in load_users() if str(u["user_id"]) != uid]
|
||||
save_users_list(users)
|
||||
self.refresh()
|
||||
QMessageBox.information(self, "提示", f"用户 {uname}(ID={uid}) 已删除")
|
||||
|
||||
def get_from_device(self):
|
||||
self.send_func(build_get_all_userid())
|
||||
|
||||
@ -78,7 +78,7 @@ CMD_NAMES = {
|
||||
CMD_VERIFY: "人脸验证",
|
||||
CMD_LED_CONTROL: "LED控制",
|
||||
CMD_ENROLL: "人脸录入(多帧)",
|
||||
CMD_ENROLL_SINGLE: "人脸录入(单帧)",
|
||||
CMD_ENROLL_SINGLE: "人脸录入",
|
||||
CMD_ENROLL_ITG: "人脸录入(集成式)",
|
||||
CMD_DELETE_USER: "删除单个用户",
|
||||
CMD_DELETE_ALL: "删除所有用户",
|
||||
@ -104,7 +104,7 @@ RESULT_NAMES = {
|
||||
0x09: "失败:超过最大用户数",
|
||||
0x0A: "失败:已录入该用户",
|
||||
0x0C: "失败:活体检测未通过",
|
||||
0x0D: "失败:超时",
|
||||
0x0D: "失败:人脸超时",
|
||||
0x0E: "失败:认证失败",
|
||||
0x13: "失败:文件读取错误",
|
||||
0x14: "失败:文件写入错误",
|
||||
@ -372,34 +372,105 @@ def parse_reply(data: bytes) -> dict:
|
||||
|
||||
# 自动生成提示消息
|
||||
if info["ok"]:
|
||||
if mid == CMD_VERIFY and "user_id" in info:
|
||||
if mid == CMD_VERIFY:
|
||||
info["message"] = (
|
||||
f"{info['mid_name']} 成功 - 用户ID: {info['user_id']}"
|
||||
)
|
||||
pass
|
||||
if mid == CMD_FACE_VIEW or mid == CMD_UVC_VIEW:
|
||||
info["message"] = f"{info['mid_name']} 成功"
|
||||
pass
|
||||
else:
|
||||
info["message"] = f"{info['mid_name']} 成功"
|
||||
inform_box : DialogInform = DialogInform()
|
||||
inform_box.information(f"{info['mid_name']}", 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']}", f"{info['result_name']} ")
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def parse_note(data: bytes) -> dict:
|
||||
"""
|
||||
NOTE 消息解析:
|
||||
- 保留工程信息(方便调试)
|
||||
- 提供用户可读提示(仅在已知状态下提示)
|
||||
"""
|
||||
if len(data) < 1:
|
||||
return {"type":"NOTE","error":"short"}
|
||||
return {"type": "NOTE", "error": "short"}
|
||||
|
||||
nid = data[0]
|
||||
rest = data[1:]
|
||||
info = {"type":"NOTE","nid": nid, "nid_name": NOTE_NAMES.get(nid, f"0x{nid:02X}")}
|
||||
info = {
|
||||
"type": "NOTE",
|
||||
"nid": nid,
|
||||
"nid_name": NOTE_NAMES.get(nid, f"0x{nid:02X}")
|
||||
}
|
||||
|
||||
user_message = None
|
||||
|
||||
# ---------- 人脸状态 ----------
|
||||
if nid == NID_FACE_STATE and len(rest) >= 16:
|
||||
vals = struct.unpack(">hhhhhhhh", rest[:16])
|
||||
# state 用 1 个字节就够,第二个字节是保留/扩展
|
||||
state = rest[0]
|
||||
sub_state = rest[1]
|
||||
|
||||
left = int.from_bytes(rest[2:4], "big", signed=True)
|
||||
top = int.from_bytes(rest[4:6], "big", signed=True)
|
||||
right = int.from_bytes(rest[6:8], "big", signed=True)
|
||||
bottom = int.from_bytes(rest[8:10], "big", signed=True)
|
||||
yaw = int.from_bytes(rest[10:12], "big", signed=True)
|
||||
pitch = int.from_bytes(rest[12:14], "big", signed=True)
|
||||
roll = int.from_bytes(rest[14:16], "big", signed=True)
|
||||
|
||||
info.update({
|
||||
"state": vals[0],
|
||||
"left": vals[1], "top": vals[2], "right": vals[3], "bottom": vals[4],
|
||||
"yaw": vals[5], "pitch": vals[6], "roll": vals[7]
|
||||
"state": state,
|
||||
"sub_state": sub_state,
|
||||
"left": left, "top": top, "right": right, "bottom": bottom,
|
||||
"yaw": yaw, "pitch": pitch, "roll": roll
|
||||
})
|
||||
|
||||
# 只处理已知状态
|
||||
state_messages = {
|
||||
0: "人脸检测正常,请保持姿势",
|
||||
1: "未检测到人脸,请面对摄像头",
|
||||
6: "请靠近摄像头",
|
||||
7: "请稍微远离摄像头",
|
||||
8: "检测到遮挡,请移开遮挡物",
|
||||
9: "请正对摄像头",
|
||||
}
|
||||
if state in state_messages:
|
||||
user_message = state_messages[state]
|
||||
|
||||
# 如果是正常状态,可以补充角度提示
|
||||
if state == 0:
|
||||
if yaw > 15:
|
||||
user_message += "(请把头向左转一些)"
|
||||
elif yaw < -15:
|
||||
user_message += "(请把头向右转一些)"
|
||||
elif pitch > 15:
|
||||
user_message += "(请抬起头)"
|
||||
elif pitch < -15:
|
||||
user_message += "(请低下头)"
|
||||
|
||||
# ---------- 其他 NOTE ----------
|
||||
elif nid == NID_READY:
|
||||
user_message = "设备已就绪,可以开始人脸识别"
|
||||
elif nid == NID_UNKNOWNERROR:
|
||||
user_message = "发生未知错误,请重试"
|
||||
elif nid == NID_OTA_DONE:
|
||||
user_message = "升级完成,请重启设备"
|
||||
elif nid == NID_EYE_STATE:
|
||||
user_message = "检测到眼睛状态变化"
|
||||
|
||||
if user_message:
|
||||
info["user_message"] = user_message
|
||||
|
||||
return info
|
||||
|
||||
|
||||
|
||||
def parse_image(data: bytes) -> dict:
|
||||
return {"type":"IMAGE","jpeg":data}
|
||||
|
||||
@ -1 +1 @@
|
||||
1,1,2025-09-13 10:06:01
|
||||
1,1,2025-09-15 15:31:32
|
||||
|
||||
|
Reference in New Issue
Block a user