Học phần: NHẬP MÔN THỊ GIÁC MÁY TÍNH VỚI PYTHON

SMART HOME CONTROLLER QUA SOCKET

Hệ thống gồm 2 thành phần độc lập giao tiếp qua mạng LAN/Wifi:

  • Edge Client (smart_home_client.py): Camera bắt cử chỉ tay bằng MediaPipe:

    • Xòe cả 5 ngón: Lệnh BẬT đèn (LIGHT_ON).

    • Nắm chặt tay (0 ngón): Lệnh TẮT đèn (LIGHT_OFF).

    • Giơ ngón trỏ + ngón giữa (Ký hiệu 2 ngón): MỞ ứng dụng giải trí/học tập (LAUNCH_APP).

  • Hub Server (smart_home_hub.py): Thiết bị trung tâm nhận lệnh qua Socket, hiển thị bảng điều khiển trạng thái đồ họa và điều khiển phần mềm/thiết bị thực tế.

Mã nguồn Đồ án 1

1. File Hub điều khiển: smart_home_hub.py

Server chạy giao diện đồ họa OpenCV mô phỏng chiếc đèn trần (sáng vàng rực rỡ khi BẬT, xám tối khi TẮT) và có thể kích hoạt mở trình duyệt/ứng dụng máy tính.

import socket
import cv2
import numpy as np
import subprocess
import webbrowser

class SmartHomeHub:
    def __init__(self, host='0.0.0.0', port=8888):
        self.host = host
        self.port = port
        self.server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        # Trạng thái thiết bị trong nhà
        self.light_state = False  # False: Tắt, True: Bật
        self.last_command = "CHỜ TÍN HIỆU..."

    def create_dashboard(self):
        """Tạo bảng điều khiển trực quan trạng thái ngôi nhà"""
        dash = np.zeros((450, 600, 3), dtype=np.uint8)
        
        # Tiêu đề Hub
        cv2.putText(dash, "SMART HOME IOT HUB", (120, 50),
                    cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255), 2)
        cv2.line(dash, (40, 70), (560, 70), (100, 100, 100), 2)

        # Vẽ bóng đèn mô phỏng
        light_color = (0, 230, 255) if self.light_state else (50, 50, 50) # Vàng sáng hoặc Xám tối
        cv2.circle(dash, (300, 180), 65, light_color, cv2.FILLED)
        cv2.circle(dash, (300, 180), 65, (200, 200, 200), 3)

        # Trạng thái chữ
        status_text = "DEN: DANG BAT (ON)" if self.light_state else "DEN: DANG TAT (OFF)"
        text_color = (0, 255, 0) if self.light_state else (0, 0, 255)
        cv2.putText(dash, status_text, (180, 290),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.8, text_color, 2)

        # Lệnh nhận gần nhất
        cv2.putText(dash, f"Lenh gan nhat: {self.last_command}", (60, 370),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)
        cv2.putText(dash, "Nhan 'Q' tren cua so de tat Hub", (130, 420),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (150, 150, 150), 1)

        return dash

    def execute_command(self, cmd):
        self.last_command = cmd
        if cmd == "LIGHT_ON":
            self.light_state = True
            print("💡 [HUB] Đã bật đèn trần.")
        elif cmd == "LIGHT_OFF":
            self.light_state = False
            print("🌑 [HUB] Đã tắt đèn trần.")
        elif cmd == "LAUNCH_APP":
            print("🚀 [HUB] Kích hoạt mở ứng dụng trình duyệt!")
            webbrowser.open("https://youtube.com")

    def run(self):
        self.server_sock.bind((self.host, self.port))
        self.server_sock.listen(1)
        self.server_sock.settimeout(0.1) # Non-blocking để vòng lặp giao diện chạy mượt
        print(f"📡 Smart Home Hub sẵn sàng tại cổng {self.port}...")

        conn = None
        while True:
            # Chờ kết nối Client
            if conn is None:
                try:
                    conn, addr = self.server_sock.accept()
                    conn.settimeout(0.05)
                    print(f"✅ Đã kết nối với Edge Camera: {addr}")
                except socket.timeout:
                    pass

            # Đọc lệnh gửi từ Client
            if conn is not None:
                try:
                    data = conn.recv(1024).decode('utf-8')
                    if data:
                        commands = data.strip().split("\n")
                        for c in commands:
                            if c:
                                self.execute_command(c)
                    else:
                        print("❌ Client ngắt kết nối.")
                        conn.close()
                        conn = None
                except socket.timeout:
                    pass
                except (ConnectionResetError, BrokenPipeError):
                    conn.close()
                    conn = None

            # Hiển thị giao diện Dashboard
            dashboard = self.create_dashboard()
            cv2.imshow("Smart Home Hub Server", dashboard)

            if cv2.waitKey(20) & 0xFF == ord('q'):
                break

        if conn:
            conn.close()
        self.server_sock.close()
        cv2.destroyAllWindows()

if __name__ == "__main__":
    hub = SmartHomeHub()
    hub.run()

2. File Camera AI điều khiển: smart_home_client.py

Client phát hiện cử chỉ tay và gửi lệnh tức thì qua Socket.

import cv2
import socket
import time
from mediapipe.python.solutions import hands as mp_hands
from mediapipe.python.solutions import drawing_utils as mp_draw

class SmartHomeClient:
    def __init__(self, hub_ip='127.0.0.1', port=8888):
        self.hub_ip = hub_ip
        self.port = port
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        
        self.cap = cv2.VideoCapture(0)
        self.hands = mp_hands.Hands(max_num_hands=1, min_detection_confidence=0.7)
        self.tip_ids = [8, 12, 16, 20]
        self.pip_ids = [6, 10, 14, 18]

        # Bộ chống gửi lặp lệnh liên tục (Debounce)
        self.last_sent_cmd = None
        self.last_sent_time = 0

    def connect(self):
        try:
            self.sock.connect((self.hub_ip, self.port))
            print("✅ Đã kết nối thành công tới Hub!")
            return True
        except Exception as e:
            print(f"❌ Không kết nối được Hub: {e}")
            return False

    def count_fingers(self, lm_list):
        if len(lm_list) == 0:
            return -1
        fingers = []
        # Ngón cái (giả định tay phải/trái giơ lòng bàn tay)
        fingers.append(1 if lm_list[4][1] < lm_list[3][1] else 0)
        # 4 ngón dài
        for tip, pip in zip(self.tip_ids, self.pip_ids):
            fingers.append(1 if lm_list[tip][2] < lm_list[pip][2] else 0)
        return fingers

    def send_command(self, cmd):
        current_time = time.time()
        # Chỉ gửi khi đổi lệnh hoặc cách lệnh cũ tối thiểu 1.5 giây
        if cmd != self.last_sent_cmd or (current_time - self.last_sent_time > 1.5):
            try:
                self.sock.sendall((cmd + "\n").encode('utf-8'))
                self.last_sent_cmd = cmd
                self.last_sent_time = current_time
                print(f"📤 Đã gửi lệnh: {cmd}")
            except Exception as e:
                print(f"❌ Lỗi gửi Socket: {e}")

    def run(self):
        if not self.connect() or not self.cap.isOpened():
            return

        print("--- ĐIỀU KHIỂN NHÀ THÔNG MINH BẰNG CỬ CHỈ ---")
        print(" -> Xòe 5 ngón: BẬT ĐÈN")
        print(" -> Nắm tay (0 ngón): TẮT ĐÈN")
        print(" -> 2 ngón (Trỏ + Giữa): MỞ TRÌNH DUYỆT")
        print(" -> Nhấn [Q] để thoát")

        while True:
            ret, frame = self.cap.read()
            if not ret:
                break

            frame = cv2.flip(frame, 1)
            rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            res = self.hands.process(rgb)

            lm_list = []
            if res.multi_hand_landmarks:
                hand = res.multi_hand_landmarks[0]
                mp_draw.draw_landmarks(frame, hand, mp_hands.HAND_CONNECTIONS)
                h, w, _ = frame.shape
                for lm in hand.landmark:
                    lm_list.append((int(lm.x * w), int(lm.y * h)))

            if lm_list:
                fingers = self.count_fingers(lm_list)
                total = fingers.count(1)

                # Phân tích cử chỉ
                if total == 5:
                    self.send_command("LIGHT_ON")
                    cv2.putText(frame, "LENH: BAT DEN", (30, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
                elif total == 0:
                    self.send_command("LIGHT_OFF")
                    cv2.putText(frame, "LENH: TAT DEN", (30, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
                elif fingers[1] == 1 and fingers[2] == 1 and fingers[3] == 0 and fingers[4] == 0:
                    self.send_command("LAUNCH_APP")
                    cv2.putText(frame, "LENH: MO UNG DUNG", (30, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 0), 2)

            cv2.imshow("Camera AI Smart Home Client", frame)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

        self.cap.release()
        self.sock.close()
        cv2.destroyAllWindows()

if __name__ == "__main__":
    client = SmartHomeClient(hub_ip='127.0.0.1', port=8888)
    client.run()