Compare commits

...

2 Commits

8 changed files with 380 additions and 152 deletions

View File

@ -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()

View File

@ -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/>

View File

@ -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/>

View File

@ -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/>

View File

@ -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 # 异步处理,直接返回
@ -876,6 +887,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", "")))
################################################################################
#刷新屏幕上的系统信息, 例如时间日期之类

View File

@ -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()
@ -107,29 +106,13 @@ class UserManageDialog(QDialog):
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())

View File

@ -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}

View File

@ -1 +1 @@
1,1,2025-09-13 10:06:01
1,1,2025-09-15 15:31:32

1 1 1 2025-09-13 10:06:01 2025-09-15 15:31:32