import sys
import cv2
import os
import time
import serial
import serial.tools.list_ports
from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox
from PySide6.QtGui import QImage, QPixmap
from PySide6.QtCore import QTimer, Qt
# 导入 UI 文件 (假设文件名为 face.py, 类名为 Ui_Form)
from face import Ui_Form
class SmartHomeWindow(QMainWindow, Ui_Form):
def __init__(self):
super().__init__()
self.setupUi(self)
# 1. 基础变量
self.cap = None
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_frame)
self.is_face_detect_on = False
self.names = ['None', 'ID:123', 'ID:123', 'ID:123', 'ID:123', 'ID:123', 'ID:123']
# 2. 串口变量
self.ser = serial.Serial()
self.serial_connected = False
# 记录开关状态
self.led_states = {'led1': False, 'led2': False, 'led3': False}
# 🟢 读取串口数据的定时器 (用于光敏数值)
self.read_timer = QTimer(self)
self.read_timer.timeout.connect(self.read_serial_data)
self.current_dir = os.path.dirname(os.path.abspath(__file__))
# 3. 界面初始化
self.init_styles()
self.init_all_images()
self.initialize_led_buttons()
self.scan_serial_ports()
# 初始化光敏显示
if hasattr(self, 'lightLabel'):
self.lightLabel.setText("0")
if hasattr(self, 'cameraLabel'): self.cameraLabel.setVisible(False)
self.load_ai_models()
# 4. 按钮绑定
if hasattr(self, 'openButton'): self.openButton.clicked.connect(self.toggle_camera)
if hasattr(self, 'recogButton'): self.recogButton.clicked.connect(self.toggle_recog)
if hasattr(self, 'saveButton'): self.saveButton.clicked.connect(self.save_image)
# LED/蜂鸣器 绑定
# 注意:这里 led1 对应 STM32 的 LED0, led2 对应 STM32 的 LED1, led3 对应 蜂鸣器
if hasattr(self, 'led1'): self.led1.clicked.connect(lambda: self.control_led('led1'))
if hasattr(self, 'led2'): self.led2.clicked.connect(lambda: self.control_led('led2'))
if hasattr(self, 'led3'): self.led3.clicked.connect(lambda: self.control_led('led3'))
# KEY按键 绑定
if hasattr(self, 'key0'): self.key0.clicked.connect(self.on_key0_pressed)
if hasattr(self, 'key1'): self.key1.clicked.connect(self.on_key1_pressed)
# 串口连接与刷新
if hasattr(self, 'connectButton'): self.connectButton.clicked.connect(self.toggle_serial_connection)
if hasattr(self, 'freshButton'): self.freshButton.clicked.connect(self.scan_serial_ports)
# ========================================================
# ⭐ 串口数据读取 (已修改以匹配 C 代码 "LIGHT:xxx")
# ========================================================
def read_serial_data(self):
"""
每100ms读取一次串口,解析光敏数值
C代码发送格式: "LIGHT:xxx\r\n"
"""
if self.serial_connected and self.ser.is_open:
try:
if self.ser.in_waiting > 0:
# 读取一行,去掉回车换行
raw_data = self.ser.readline().decode('utf-8', errors='ignore').strip()
if raw_data:
# 🟢 修改点:解析 "LIGHT:" 开头的数据
if raw_data.startswith("LIGHT:"):
try:
# 分割字符串获取数值部分 (例如 "LIGHT:85" -> "85")
light_val = raw_data.split(":")[1]
if hasattr(self, 'lightLabel'):
self.lightLabel.setText(light_val)
except IndexError:
pass
# 调试用:打印非LIGHT开头的消息(比如 STM32 的回显 "Received: 0")
# else:
# print(f"STM32 Message: {raw_data}")
except Exception:
pass
# ========================================================
# 按键逻辑
# ========================================================
def on_key0_pressed(self):
print("UI: KEY0 被按下 - 切换 LED1(STM32_LED0) 状态")
self.control_led('led1')
def on_key1_pressed(self):
print("UI: KEY1 被按下 - 切换 LED2(STM32_LED1) 状态")
self.control_led('led2')
# ========================================================
# ⭐ 控制逻辑 (已修改为发送 0-5)
# ========================================================
def control_led(self, led_name):
self.led_states[led_name] = not self.led_states[led_name]
is_on = self.led_states[led_name]
btn = getattr(self, led_name)
command = ""
# 🟢 UI 更新与指令生成
if led_name == 'led3':
# --- 蜂鸣器 (对应 C 代码 4 和 5) ---
status_text = "开启" if is_on else "关闭"
print(f"UI: 点击了 蜂鸣器 - 状态: {status_text}")
if is_on:
btn.setStyleSheet("background-color: #FF3399; color: white; border-radius: 5px; font-weight: bold;")
btn.setText("蜂鸣器开")
command = "4" # 发送 4: 蜂鸣器响
else:
btn.setStyleSheet("background-color: rgb(0, 170, 255); color: white; border-radius: 5px;")
btn.setText("蜂鸣器关")
command = "5" # 发送 5: 蜂鸣器停
else:
# --- LED 控制 ---
display_name = led_name.upper()
status_text = "开启" if is_on else "关闭"
print(f"UI: 点击了 {display_name} 按钮 - 状态: {status_text}")
if is_on:
btn.setStyleSheet("background-color: red; color: white; border-radius: 5px; font-weight: bold;")
btn.setText(f"{display_name}开")
else:
btn.setStyleSheet("background-color: rgb(0, 170, 255); color: white; border-radius: 5px;")
btn.setText(f"{display_name}关")
# 🟢 修改点:匹配 STM32 的数字指令
if led_name == 'led1':
# Python led1 -> STM32 LED0
command = "0" if is_on else "1"
elif led_name == 'led2':
# Python led2 -> STM32 LED1
command = "2" if is_on else "3"
# 发送指令
self.send_data(command)
# ========================================================
# 串口连接管理
# ========================================================
def scan_serial_ports(self):
if self.serial_connected:
print("正在刷新... 检测到已连接,正在自动断开...")
self.toggle_serial_connection()
if not hasattr(self, 'comboBox'): return
self.comboBox.clear()
ports = serial.tools.list_ports.comports()
if not ports:
self.comboBox.addItem("无串口")
else:
for p in ports:
self.comboBox.addItem(p.device)
print("串口列表已刷新")
def toggle_serial_connection(self):
status_label = getattr(self, 'connectLabel', getattr(self, 'connect', None))
if not self.serial_connected:
# === 打开 ===
current_port = self.comboBox.currentText()
if not current_port or current_port == "无串口":
QMessageBox.warning(self, "提示", "请选择有效的串口!")
return
try:
self.ser.baudrate = 115200 # ⚠️ 注意:通常 STM32 默认是 115200,如果您的代码配置是 9600 请改回 9600
self.ser.port = current_port
self.ser.timeout = 0.1
self.ser.open()
# DTR/RTS 设置取决于您的硬件复位电路,通常设为 False 以防自动复位
self.ser.dtr = False; self.ser.rts = False
self.serial_connected = True
# 开启读取定时器
self.read_timer.start(100)
self.connectButton.setText("关闭串口")
self.connectButton.setStyleSheet("background-color: #2ecc71; color: white; border-radius: 5px;")
if status_label:
status_label.setText("已连接")
status_label.setStyleSheet("background-color: #2ecc71; color: white; border-radius: 3px;")
print(f"串口 {current_port} 连接成功")
except Exception as e:
QMessageBox.critical(self, "错误", f"无法打开串口 {current_port}\n错误: {e}")
else:
# === 关闭 ===
try:
self.read_timer.stop()
self.ser.close()
except: pass
finally:
self.serial_connected = False
self.connectButton.setText("打开串口")
self.connectButton.setStyleSheet(self.pink_btn_style)
if status_label:
status_label.setText("断开")
status_label.setStyleSheet("background-color: #e74c3c; color: white; border-radius: 3px;")
print("串口已断开")
def send_data(self, command):
if self.serial_connected and self.ser.is_open:
try:
# 发送字符串
self.ser.write(command.encode('utf-8'))
print(f"发送数据: {command}")
except Exception as e:
print(f"发送异常: {e}")
self.toggle_serial_connection()
QMessageBox.warning(self, "连接断开", "串口通信异常,已自动断开连接。")
# ========================================================
# 模型与视频 (保持不变)
# ========================================================
def load_ai_models(self):
try:
xml = cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
self.face_cascade = cv2.CascadeClassifier(xml)
self.recognizer = cv2.face.LBPHFaceRecognizer_create()
possible_paths = [
os.path.join(self.current_dir, "..", "trainer", "trainer3.yml"),
os.path.join(self.current_dir, "trainer", "trainer3.yml"),
os.path.join(self.current_dir, "trainer3.yml"),
os.path.join(self.current_dir, "..", "trainer", "trainer.yml")
]
self.is_model_trained = False
for path in possible_paths:
norm_path = os.path.normpath(path)
if os.path.exists(norm_path):
self.recognizer.read(norm_path)
self.is_model_trained = True
print(f"✅ 成功加载模型: {norm_path}")
break
if not self.is_model_trained:
print("❌ 警告: 未找到 trainer3.yml")
except Exception as e:
print(f"❌ 模型加载出错: {e}")
def update_frame(self):
ret, frame = self.cap.read()
if ret:
frame = cv2.flip(frame, 1)
if self.is_face_detect_on:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = self.face_cascade.detectMultiScale(gray, 1.2, 5)
if len(faces) > 0:
largest_face = max(faces, key=lambda rect: rect[2] * rect[3])
faces = [largest_face]
for (x, y, w, h) in faces:
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
if self.is_model_trained:
try:
id, confidence = self.recognizer.predict(gray[y:y+h, x:x+w])
if confidence < 100:
name_txt = self.names[id] if id < len(self.names) else "User"
else:
name_txt = "Unknown"
cv2.putText(frame, str(name_txt), (x+5, y-5), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2)
except: pass
else:
cv2.putText(frame, "Face", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgb.shape
img = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888)
if hasattr(self, 'cameraLabel'):
self.cameraLabel.setPixmap(QPixmap.fromImage(img).scaled(
self.cameraLabel.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
def toggle_camera(self):
if self.timer.isActive():
self.timer.stop()
if self.cap: self.cap.release()
self.openButton.setText("打开摄像头")
self.openButton.setStyleSheet(self.pink_btn_style)
if hasattr(self, 'cameraLabel'): self.cameraLabel.setVisible(False)
else:
self.cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
if self.cap.isOpened():
self.timer.start(30)
self.openButton.setText("关闭摄像头")
self.openButton.setStyleSheet("background-color: #e74c3c; color: white; border-radius: 5px;")
if hasattr(self, 'cameraLabel'): self.cameraLabel.setVisible(True)
def toggle_recog(self):
if not self.timer.isActive(): return
self.is_face_detect_on = not self.is_face_detect_on
if self.is_face_detect_on:
self.recogButton.setStyleSheet("background-color: #FF3399; color: white; border-radius: 5px;")
else:
self.recogButton.setStyleSheet(self.pink_btn_style)
def init_styles(self):
self.pink_btn_style = "QPushButton { background-color: #FF99CC; color: white; border-radius: 5px; font-weight: bold; border: none; } QPushButton:hover { background-color: #FF66B2; }"
for btn in ['openButton', 'saveButton', 'recogButton', 'connectButton']:
if hasattr(self, btn): getattr(self, btn).setStyleSheet(self.pink_btn_style)
def initialize_led_buttons(self):
for n in ['led1', 'led2']:
if hasattr(self, n):
getattr(self, n).setStyleSheet("background-color: rgb(0, 170, 255); color: white; border-radius: 5px;")
getattr(self, n).setText(f"{n.upper()}关")
if hasattr(self, 'led3'):
self.led3.setStyleSheet("background-color: rgb(0, 170, 255); color: white; border-radius: 5px;")
self.led3.setText("蜂鸣器关")
if hasattr(self, 'connectLabel'):
self.connectLabel.setText("断开")
self.connectLabel.setStyleSheet("background-color: #e74c3c; color: white; border-radius: 3px;")
def init_all_images(self):
img_dirs = [os.path.join(self.current_dir, "img"), self.current_dir]
self.set_image("1.jpg", "bannerLabel", img_dirs)
self.set_image("2.jpg", "logoLabel", img_dirs)
self.set_image("3.jpg", "imgLabel1", img_dirs)
self.set_image("4.jpg", "imgLabel2", img_dirs)
def set_image(self, fname, label_name, dirs):
if not hasattr(self, label_name): return
for d in dirs:
p = os.path.join(d, fname)
if os.path.exists(p):
getattr(self, label_name).setPixmap(QPixmap(p))
getattr(self, label_name).setScaledContents(True)
return
def save_image(self):
if self.cap and self.cap.read()[0]: cv2.imwrite(f"photo_{int(time.time())}.jpg", self.cap.read()[1])
def closeEvent(self, event):
if self.cap: self.cap.release()
if self.ser.is_open: self.ser.close()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SmartHomeWindow()
window.show()
sys.exit(app.exec())修改代码使当检测到人脸时蜂鸣器自动报警生成完整代码
最新发布