diff --git a/QT5_Project/KD_ZM_8_XCF/APPWindow.py b/QT5_Project/KD_ZM_8_XCF/APPWindow.py index 210c8c1..d6c3a17 100644 --- a/QT5_Project/KD_ZM_8_XCF/APPWindow.py +++ b/QT5_Project/KD_ZM_8_XCF/APPWindow.py @@ -453,6 +453,7 @@ class QFaceCameraViewPage(PageTemplate): self.t_video.start() self.face_verify_result = None # 用于存储人脸验证结果 + self.verify_in_progress = False # 标志当前是否有验证在进行中 self.auto_connect_serial() @@ -541,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") == CMD_ENROLL_ITG: - if info.get("result") == 0x00: - user_id = info.get("user_id") - if user_id: - if save_user(user_id): - self.log(f"[INFO] 用户ID={user_id} 已保存") - else: - inform_box : DialogInform = DialogInform() - inform_box.information("提示", 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): @@ -595,8 +664,6 @@ class QFaceCameraViewPage(PageTemplate): self.close_serial() super().closeEvent(e) - - #P06故障查询页面 QFaultQueryPage class QFaultQueryPage(PageTemplate): def __init__(self, parent_window): @@ -774,7 +841,7 @@ class APPWindow(QMainWindow): QFaceCameraViewPage.video_worker = None QFaceCameraViewPage.ser = None - + self.showFullScreen() self.menu_sequence_list : PageTemplate = [] diff --git a/QT5_Project/KD_ZM_8_XCF/UI/P00DeviceList.ui b/QT5_Project/KD_ZM_8_XCF/UI/P00DeviceList.ui index b1dffef..9556aae 100644 --- a/QT5_Project/KD_ZM_8_XCF/UI/P00DeviceList.ui +++ b/QT5_Project/KD_ZM_8_XCF/UI/P00DeviceList.ui @@ -3037,7 +3037,7 @@ color: rgb(255, 170, 0); - Index=7, Action=SetPage5_1,SelectImag=IMxx_00D.png + Index=7, Action=SetPage5_1,SelectImag=IMxx_00D.png,password diff --git a/QT5_Project/KD_ZM_8_XCF/UI/P05_01_FaceCameraView.ui b/QT5_Project/KD_ZM_8_XCF/UI/P05_01_FaceCameraView.ui index 9d929f7..90c3a56 100644 --- a/QT5_Project/KD_ZM_8_XCF/UI/P05_01_FaceCameraView.ui +++ b/QT5_Project/KD_ZM_8_XCF/UI/P05_01_FaceCameraView.ui @@ -91,39 +91,13 @@ false - - - - 0 - 0 - 211 - 41 - - - - - - - background-color: rgb(0, 0, 0); -color: rgb(170, 0, 0); - - - 日志 - - - - 72 - 144 - - - -20 - 60 + 20 701 - 301 + 341 @@ -182,7 +156,7 @@ color: rgb(170, 0, 0); - + mian=51 命令 @@ -197,7 +171,7 @@ color: rgb(170, 0, 0); - Index=0, Action=Reset,SelectImag=IMxx_00F.png + Index=0, Action=Reset,SelectImag=IMxx_00F.png,groupstart=8 复位 @@ -213,65 +187,33 @@ color: rgb(170, 0, 0); - Index=0, Action=VideoMode,SelectImag=IMxx_00F.png + Index=1, Action=VideoMode,SelectImag=IMxx_00F.png 视频模式 - - - - 12 - 115 - 80 - 23 - - - - Index=0, Action=Verify,password,SelectImag=IMxx_00F.png - - - 识别 - - - 12 - 144 + 10 + 210 80 23 - Index=0, Action=EnrollItgSingle,SelectImag=IMxx_00F.png + Index=5, Action=EnrollItgSingle,SelectImag=IMxx_00F.png ITG注册 - - - - 12 - 173 - 80 - 23 - - - - Index=0, Action=Users,SelectImag=IMxx_00F.png - - - 用户管理 - - - 0 - 310 + -10 + 180 120 25 @@ -286,7 +228,7 @@ color: rgb(170, 0, 0); Qt::NoFocus - System=FaceRecogTimeout, Action=ModifySystem, Index=13,SelectImag=P4_ParaSelect.png, password + System=FaceRecogTimeout, Action=ModifySystem, Index=4,SelectImag=P4_ParaSelect.png, password background-color: rgba(85, 170, 127,0); @@ -306,13 +248,13 @@ color: rgb(177, 229, 252); 10 - 210 + 90 80 23 - Index=0, Action=ConnectCamera,password,SelectImag=IMxx_00F.png + Index=2, Action=ConnectCamera,SelectImag=IMxx_00F.png 打开视频 @@ -322,13 +264,13 @@ color: rgb(177, 229, 252); 10 - 90 + 120 80 23 - Index=0, Action=FaceBox,SelectImag=IMxx_00F.png + Index=3, Action=FaceBox,SelectImag=IMxx_00F.png 人脸框 @@ -344,12 +286,28 @@ color: rgb(177, 229, 252); - Index=0, Action=DeleteUser,SelectImag=IMxx_00F.png + Index=6, Action=DeleteUser,SelectImag=IMxx_00F.png 删除用户 + + + + 10 + 270 + 80 + 23 + + + + Index=7, Action=UserCount,SelectImag=IMxx_00F.png,groupend=8 + + + 查看用户 + + diff --git a/QT5_Project/KD_ZM_8_XCF/UIFrameWork.py b/QT5_Project/KD_ZM_8_XCF/UIFrameWork.py index 8771e71..3e94038 100644 --- a/QT5_Project/KD_ZM_8_XCF/UIFrameWork.py +++ b/QT5_Project/KD_ZM_8_XCF/UIFrameWork.py @@ -42,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 ) @@ -829,8 +829,6 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): 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 @@ -840,8 +838,6 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): 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() @@ -963,16 +959,17 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): pd_val = 0 timeout_val = system_parameter().get_verify_timeout() 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_() @@ -1046,7 +1043,7 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): else: self.log("[WARN] 串口未连接,无法控制人脸框") - def delete_user_by_id(self, CSV_FILE = CSV_FILE): + def delete_user_by_id(self, CSV_FILE=CSV_FILE): users = load_users() # 弹出对话框选择用户ID @@ -1064,22 +1061,27 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): DialogInform(self).information("提示", "请输入有效数字ID") return - # 查找用户 - user = next((u for u in users if u["user_id"] == user_id), None) + 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️⃣ 下发删除命令 - self.send(build_delete_user(user_id)) + # 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")) != str(user_id)] + 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) @@ -1091,12 +1093,12 @@ class UIFrameWork(QMainWindow, class_comm_mqtt_interface): return # 3️⃣ 更新内存用户列表 - users = [u for u in users if u["user_id"] != user_id] + 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()) diff --git a/QT5_Project/Shared_CODE/FaceRecognitionProtocol.py b/QT5_Project/Shared_CODE/FaceRecognitionProtocol.py index 14da4ea..3a6412e 100644 --- a/QT5_Project/Shared_CODE/FaceRecognitionProtocol.py +++ b/QT5_Project/Shared_CODE/FaceRecognitionProtocol.py @@ -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} diff --git a/QT5_Project/Shared_UI/users.csv b/QT5_Project/Shared_UI/users.csv index aa1e8c7..7f226d9 100644 --- a/QT5_Project/Shared_UI/users.csv +++ b/QT5_Project/Shared_UI/users.csv @@ -1,3 +1 @@ -1,1,2025-09-15 10:48:28 -2,2,2025-09-15 10:56:46 -3,3,2025-09-15 11:00:04 +1,1,2025-09-15 15:31:32