File size: 5,389 Bytes
f82e529
 
d050c18
f82e529
d050c18
028d3bc
d050c18
028d3bc
f82e529
d050c18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f82e529
 
d050c18
 
f82e529
 
d050c18
 
 
 
 
 
0ac295f
0f73f4b
f82e529
1ca5363
f82e529
 
d050c18
f82e529
d050c18
f82e529
1ca5363
f82e529
d050c18
faebfab
 
d0c5be5
d050c18
 
1ca5363
d0c5be5
 
d050c18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f82e529
d050c18
 
 
 
 
f82e529
d050c18
f82e529
d050c18
f82e529
d050c18
 
 
 
46bcfd1
1ca5363
96e8c3f
d050c18
46bcfd1
d050c18
 
 
46bcfd1
 
 
d050c18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f783696
d050c18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import gradio as gr
import pandas as pd
from matplotlib.figure import Figure

from config import UI_UPDATE_INTERVAL_SECONDS, GAS_CHART_Y_LIM, GAS_CAL_SECONDS
from ai.face_id import FaceID
from ai.gas import EnvAI
from mqtt_client import MQTTService

def make_fig(df: pd.DataFrame):
    # Dual-axis plot: Gas (left), Temp (right)
    fig = Figure(figsize=(7.2, 3.4))
    ax = fig.subplots()
    x = df["T"].values

    ax.plot(x, df["Gas"].values, color="#58a6ff", linewidth=1.6, label="Gas")
    ax.set_ylim(GAS_CHART_Y_LIM)
    ax.set_xlabel("Time (s)")
    ax.set_ylabel("Gas (0–4095)")
    ax.grid(True, alpha=0.25)

    ax2 = ax.twinx()
    ax2.plot(x, df["Temp"].values, color="#ffa657", linewidth=1.6, label="Temp")
    ax2.set_ylabel("Temp (°C)")
    ax2.set_ylim(0, 100)

    ax.legend(loc="upper left")
    ax2.legend(loc="upper right")
    return fig

def build_app():
    face = FaceID()
    env = EnvAI()
    mqtt = MQTTService(on_metrics_message=env.handle_metrics_json)
    mqtt.start()

    css = """
    .gradio-container { background-color: #0b0f19 !important; color: white !important; }
    #card { background: #161b22 !important; border: 1px solid #30363d !important; border-radius: 12px !important; padding: 20px !important; }
    h1 { color: #58a6ff !important; text-shadow: 0 0 10px rgba(88, 166, 255, 0.3); }
    """

    with gr.Blocks() as demo:
        gr.HTML("<h1 style='text-align: center;'>🛡️ Smart Home AI Command Center</h1>")

        timer = gr.Timer(UI_UPDATE_INTERVAL_SECONDS)

        with gr.Row():
            with gr.Column(elem_id="card", scale=1):
                webcam = gr.Image(sources=["webcam"], type="numpy", label="Security Cam")
                btn = gr.Button("🚀 NHẬN DIỆN", variant="primary")
                name_out = gr.Textbox(label="Danh tính")
                mqtt_out = gr.Textbox(label="Lệnh IoT")

                tpl_file = gr.File(label="Tải template (.npz)", file_types=[".npz"])
                tpl_status = gr.Textbox(label="Trạng thái Template", interactive=False)

                with gr.Row():
                    blue_on  = gr.Button("💡 Bật đèn Blue")
                    blue_off = gr.Button("💡 Tắt đèn Blue")
                    door_open = gr.Button("🔓 Mở cửa 30s")
                    door_lock = gr.Button("🔒 Khóa cửa")

                gr.Markdown("### Gas IsolationForest Controls")
                with gr.Row():
                    cal_btn = gr.Button(f"🧪 Calibrate Gas ({GAS_CAL_SECONDS}s)")
                    reset_btn = gr.Button("🔄 Reset Gas IF")
                cal_status = gr.Textbox(label="IF Status", interactive=False)
                gas_if_flag = gr.Checkbox(label="IF Gas Anomaly", interactive=False)

            with gr.Column(elem_id="card", scale=2):
                plot = gr.Plot(value=make_fig(pd.DataFrame({"T": [0.0], "Gas": [0.0], "Temp": [0.0]})),
                               label="Gas & Temperature")
                with gr.Row():
                    num_out = gr.Number(label="Gas hiện tại", precision=0)
                    stat_out = gr.Textbox(label="Phân tích môi trường")
                with gr.Row():
                    red_led = gr.Checkbox(label="Gas Alert (Red)", interactive=False)
                    green_led = gr.Checkbox(label="Temp Alert (Green)", interactive=False)
                    blue_led = gr.Checkbox(label="Blue LED", interactive=False)
                    special = gr.Checkbox(label="Special Danger", interactive=False)

        # Timer tick
        def ui_tick():
            df, gas_val, summary, red, green, blue, spec, gas_if, cal_msg, calibrating = env.ui_snapshot()
            fig = make_fig(df)
            # If actively calibrating, show countdown message
            status_msg = cal_msg if cal_msg else ("⏳ Đang hiệu chuẩn..." if calibrating else "")
            return fig, gas_val, summary, bool(red), bool(green), bool(blue), bool(spec), bool(gas_if), status_msg

        timer.tick(fn=ui_tick, outputs=[plot, num_out, stat_out, red_led, green_led, blue_led, special, gas_if_flag, cal_status])

        # FaceID
        def on_recognize(frame):
            label, info, cmd = face.recognize(frame)
            if cmd is not None:
                mqtt.publish_door(cmd)
            return label, info

        btn.click(fn=on_recognize, inputs=webcam, outputs=[name_out, mqtt_out], concurrency_limit=1)

        # Template upload
        def on_template_uploaded(file):
            if file is None:
                return "❌ Chưa chọn file .npz"
            return face.set_template_file(file.name)

        tpl_file.change(fn=on_template_uploaded, inputs=tpl_file, outputs=tpl_status)

        # Blue LED controls
        blue_on.click(lambda: mqtt.publish_blue_led(True), outputs=[])
        blue_off.click(lambda: mqtt.publish_blue_led(False), outputs=[])

        # Door controls
        door_open.click(lambda: mqtt.publish_door("OPEN"), outputs=[])
        door_lock.click(lambda: mqtt.publish_door("LOCK"), outputs=[])

        # Gas IF controls
        def on_calibrate():
            return env.start_gas_calibration(GAS_CAL_SECONDS)

        def on_reset_if():
            return env.reset_gas_if()

        cal_btn.click(fn=on_calibrate, outputs=[cal_status])
        reset_btn.click(fn=on_reset_if, outputs=[cal_status])

    return demo, css