IoTLabs

Nghiên cứu, Sáng tạo và Thử nghiệm

Hệ thống giám sát nhà thông minh mini với Raspberry Pi – Bài 5: Gửi cảnh báo và đẩy event lên MQTT, dashboard

Ở bài trước, chúng ta đã có một hệ thống giám sát cửa và chuyển động khá hoàn chỉnh ở mức local:

  • Raspberry Pi đọc cảm biến cửa từ và PIR
  • Python tạo event
  • rule engine quyết định khi nào cần cảnh báo
  • hệ thống phân biệt armed / disarmed
  • có cooldown để giảm spam alert

Tuy nhiên, nếu mọi thứ chỉ dừng ở terminal hoặc file log local, hệ thống vẫn còn thiếu một phần rất quan trọng:

👉 khả năng giao tiếp với bên ngoài

Một hệ thống giám sát thực tế thường cần làm được ít nhất một trong các việc sau:

  • gửi cảnh báo ngay đến người dùng
  • đẩy event sang hệ thống khác
  • publish trạng thái lên MQTT broker
  • hiển thị tình trạng cửa và chuyển động trên dashboard
  • lưu lịch sử để xem lại sau

Đó là mục tiêu của bài này.

Trong bài này, chúng ta sẽ nâng cấp project từ một hệ thống theo dõi local thành một hệ thống có thể:

  • gửi notify
  • publish event lên MQTT
  • đồng bộ trạng thái lên dashboard

Đây cũng là bước cuối để hoàn thiện mini series 24 theo đúng tư duy IoT:

sensor -> event -> rule -> notify -> integration

Bạn sẽ làm được gì sau bài này

Sau khi hoàn thành bài này, bạn sẽ có thể:

  • gửi cảnh báo từ Raspberry Pi ra bên ngoài
  • publish event cửa và PIR lên MQTT
  • publish trạng thái hiện tại của hệ thống
  • đẩy dữ liệu sang dashboard hoặc backend
  • hoàn thiện một hệ thống giám sát nhà thông minh mini có khả năng mở rộng thật sự

Vì sao cần notify và MQTT

Một event local như:

door_open

chỉ thực sự có giá trị khi nó đến được đúng nơi cần đến.

Ví dụ:

  • chủ nhà cần nhận được thông báo trên điện thoại
  • dashboard cần biết cửa đang mở hay đóng
  • backend cần ghi lại lịch sử hoạt động
  • một ESP32 khác cần biết hệ thống đang armed hay disarmed

Nếu không có lớp giao tiếp ra ngoài, Raspberry Pi chỉ giống như một thiết bị tự biết chuyện của mình, nhưng không chia sẻ được với các thành phần khác.

MQTT và notify giúp giải quyết đúng bài toán này.

Kiến trúc hoàn chỉnh của hệ thống

Sau khi thêm notify và MQTT, kiến trúc của Hệ thống giám sát nhà thông minh mini sẽ như sau:

Door Sensor + PIR -> Raspberry Pi -> Event Monitor -> Rule Engine -> Notify / MQTT / Dashboard

Trong đó:

  • Event Monitor: đọc GPIO và tạo event
  • Rule Engine: quyết định alert hay chỉ log
  • Notify: gửi cảnh báo cho người dùng
  • MQTT: publish event và trạng thái
  • Dashboard: hiển thị dữ liệu theo thời gian thực

Đây là một kiến trúc rất thực tế và cũng rất hợp với các dự án smart home mini.

Những gì nên được gửi ra ngoài

Không phải mọi thứ đều cần gửi notify.
Ta nên phân biệt rõ 3 loại dữ liệu:

1. Event thô

Là các sự kiện do cảm biến sinh ra:

  • door_open
  • door_closed
  • motion_detected
  • motion_idle

Loại này phù hợp để:

  • publish MQTT
  • lưu lịch sử
  • hiển thị timeline trên dashboard

2. Alert

Là các cảnh báo đã qua rule engine:

  • door_open_alert
  • motion_alert

Loại này phù hợp để:

  • gửi Telegram / email / webhook
  • hiển thị dạng notification nổi bật
  • đẩy tới backend xử lý tiếp

3. Current state

Là trạng thái mới nhất của hệ thống:

  • cửa đang mở hay đóng
  • có chuyển động hay không
  • hệ thống đang armed hay disarmed

Loại này phù hợp để:

  • dashboard hiển thị trạng thái realtime
  • các service khác query nhanh mà không cần đọc lại toàn bộ event log

Đây là cách tư duy rất quan trọng:
event, alert và state là ba lớp dữ liệu khác nhau.

Chọn cách gửi cảnh báo nào

Trong bài này, ta có thể chia notify thành ba hướng phổ biến:

Telegram

Rất tiện cho demo và cá nhân:

  • nhanh
  • dễ nhận
  • gần realtime

Email

Phù hợp nếu cần lưu lại hoặc gửi kiểu trang trọng hơn, nhưng chậm hơn notify tức thời.

Webhook

Phù hợp khi muốn đẩy alert sang:

  • backend
  • automation service
  • app khác
  • workflow nội bộ

Để dễ hiểu và dễ mở rộng, trong bài này chúng ta sẽ xây code theo kiểu có một Notification Service, để về sau bạn thay Telegram, email hay webhook đều dễ.

Thiết kế topic MQTT cho hệ thống giám sát

Đây là phần rất quan trọng.
Nếu topic MQTT được đặt tốt ngay từ đầu, hệ thống sẽ dễ mở rộng hơn nhiều.

Nhóm 1: Event

smart-home/events/door
smart-home/events/motion

Nhóm 2: Alert

smart-home/alerts/security

Nhóm 3: State

smart-home/state/door
smart-home/state/motion
smart-home/state/system

Cách đặt rõ hơn theo site hoặc room

Nếu sau này hệ thống lớn hơn, bạn có thể mở rộng thành:

iotlabs/home1/frontdoor/events
iotlabs/home1/livingroom/motion
iotlabs/home1/security/alerts
iotlabs/home1/system/state

Trong bài này, để giữ code gọn và rõ, ta sẽ dùng bộ topic đơn giản:

  • smart-home/events/door
  • smart-home/events/motion
  • smart-home/alerts/security
  • smart-home/state/door
  • smart-home/state/motion
  • smart-home/state/system

Thiết kế payload gửi MQTT

Payload nên có cấu trúc rõ ràng, tránh chỉ gửi text thô.

Ví dụ event cửa

{
  "ts": "2026-04-11 21:30:00",
  "sensor": "door",
  "event": "door_open"
}

Ví dụ event PIR

{
  "ts": "2026-04-11 21:30:05",
  "sensor": "pir",
  "event": "motion_detected"
}

Ví dụ alert

{
  "ts": "2026-04-11 21:30:05",
  "type": "motion_alert",
  "level": "high",
  "message": "Motion detected while system is armed"
}

Ví dụ state cửa

{
  "ts": "2026-04-11 21:30:10",
  "sensor": "door",
  "state": "open"
}

Ví dụ state hệ thống

{
  "ts": "2026-04-11 21:30:10",
  "armed": true
}

Payload kiểu này vừa dễ đọc, vừa dễ dùng cho dashboard hoặc backend.

Cài thư viện MQTT cho Python

Chúng ta sẽ dùng Paho MQTT client.

Cài bằng lệnh:

pip install paho-mqtt

Tạo MQTT service đơn giản

Tạo file:

mqtt_service.py

Code mẫu

import json
import paho.mqtt.client as mqtt

BROKER_HOST = "127.0.0.1"
BROKER_PORT = 1883

class MQTTService:
    def __init__(self):
        self.client = mqtt.Client()
        self.client.connect(BROKER_HOST, BROKER_PORT, 60)

    def publish(self, topic, payload):
        self.client.publish(topic, json.dumps(payload))
        print(f"MQTT -> {topic}: {payload}")

Code này đủ để:

  • connect broker
  • publish bất kỳ event, alert hay state nào

Gửi event lên MQTT

Giả sử bạn có event cửa:

event = {
    "ts": "2026-04-11 21:30:00",
    "sensor": "door",
    "event": "door_open"
}

Ta publish như sau:

mqtt_service.publish("smart-home/events/door", event)

Với PIR:

event = {
    "ts": "2026-04-11 21:30:05",
    "sensor": "pir",
    "event": "motion_detected"
}

mqtt_service.publish("smart-home/events/motion", event)

Gửi current state lên MQTT

Ngoài event, bạn nên publish trạng thái mới nhất.

State cửa

door_state_payload = {
    "ts": "2026-04-11 21:30:00",
    "sensor": "door",
    "state": "open"
}

mqtt_service.publish("smart-home/state/door", door_state_payload)

State PIR

motion_state_payload = {
    "ts": "2026-04-11 21:30:05",
    "sensor": "motion",
    "state": "detected"
}

mqtt_service.publish("smart-home/state/motion", motion_state_payload)

State hệ thống

system_state_payload = {
    "ts": "2026-04-11 21:30:05",
    "armed": True
}

mqtt_service.publish("smart-home/state/system", system_state_payload)

Vì sao cần state riêng

Vì dashboard thường chỉ cần biết:

  • trạng thái hiện tại là gì

Thay vì phải đọc lại toàn bộ event log.

Tạo Notification Service

Bây giờ ta xây một lớp gửi cảnh báo.

Tạo file:

notification_service.py

Code mẫu đơn giản

class NotificationService:
    def send(self, alert):
        print("NOTIFY:", alert)

Ở giai đoạn đầu, ta chỉ in ra terminal.
Sau đó bạn có thể thay bằng:

  • Telegram API
  • email
  • webhook

Điểm quan trọng là các phần khác của hệ thống không cần biết cách gửi cụ thể ra sao. Chúng chỉ cần gọi:

notification_service.send(alert)

Tích hợp rule engine + notify + MQTT

Giả sử ta có event monitor tạo ra event như sau:

event = {
    "ts": now_str(),
    "sensor": "door",
    "event": "door_open"
}

Luồng xử lý sẽ là:

  1. publish event
  2. publish current state
  3. truyền event cho rule engine
  4. nếu có alert thì gửi notify
  5. đồng thời publish alert lên MQTT

Ví dụ xử lý event cửa

def handle_event(event):
    if event["sensor"] == "door":
        mqtt_service.publish("smart-home/events/door", event)

        state_payload = {
            "ts": event["ts"],
            "sensor": "door",
            "state": "open" if event["event"] == "door_open" else "closed"
        }
        mqtt_service.publish("smart-home/state/door", state_payload)

    elif event["sensor"] == "pir":
        mqtt_service.publish("smart-home/events/motion", event)

        state_payload = {
            "ts": event["ts"],
            "sensor": "motion",
            "state": "detected" if event["event"] == "motion_detected" else "idle"
        }
        mqtt_service.publish("smart-home/state/motion", state_payload)

    alert = rule_engine.process_event(event)

    if alert:
        notification_service.send(alert)
        mqtt_service.publish("smart-home/alerts/security", alert)

Đây là một flow rất sạch và đúng kiến trúc.

Code mẫu tích hợp hoàn chỉnh

from datetime import datetime
from mqtt_service import MQTTService
from notification_service import NotificationService
from rule_engine import RuleEngine

mqtt_service = MQTTService()
notification_service = NotificationService()
rule_engine = RuleEngine()

def now_str():
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

def handle_event(event):
    if event["sensor"] == "door":
        mqtt_service.publish("smart-home/events/door", event)

        state_payload = {
            "ts": event["ts"],
            "sensor": "door",
            "state": "open" if event["event"] == "door_open" else "closed"
        }
        mqtt_service.publish("smart-home/state/door", state_payload)

    elif event["sensor"] == "pir":
        mqtt_service.publish("smart-home/events/motion", event)

        state_payload = {
            "ts": event["ts"],
            "sensor": "motion",
            "state": "detected" if event["event"] == "motion_detected" else "idle"
        }
        mqtt_service.publish("smart-home/state/motion", state_payload)

    system_state_payload = {
        "ts": event["ts"],
        "armed": rule_engine.armed
    }
    mqtt_service.publish("smart-home/state/system", system_state_payload)

    alert = rule_engine.process_event(event)

    if alert:
        notification_service.send(alert)
        mqtt_service.publish("smart-home/alerts/security", alert)

# Demo thử
handle_event({
    "ts": now_str(),
    "sensor": "door",
    "event": "door_open"
})

handle_event({
    "ts": now_str(),
    "sensor": "pir",
    "event": "motion_detected"
})

Cách kiểm tra bằng MQTT subscriber

Bạn có thể dùng mosquitto_sub để xem message.

Subscribe event cửa

mosquitto_sub -h 127.0.0.1 -t smart-home/events/door

Subscribe event chuyển động

mosquitto_sub -h 127.0.0.1 -t smart-home/events/motion

Subscribe alert

mosquitto_sub -h 127.0.0.1 -t smart-home/alerts/security

Subscribe state hệ thống

mosquitto_sub -h 127.0.0.1 -t smart-home/state/system

Khi test, bạn sẽ thấy từng nhóm dữ liệu đi đúng topic.

Dashboard sẽ dùng dữ liệu nào

Một dashboard đơn giản cho hệ thống này thường cần 3 phần chính.

1. Current status

Hiển thị:

  • cửa đang mở hay đóng
  • có chuyển động hay không
  • hệ thống đang armed hay disarmed

Dữ liệu lấy từ:

  • smart-home/state/door
  • smart-home/state/motion
  • smart-home/state/system

2. Timeline event

Hiển thị lịch sử gần nhất:

  • cửa mở lúc nào
  • chuyển động xuất hiện lúc nào
  • có bao nhiêu event trong ngày

Dữ liệu lấy từ:

  • smart-home/events/door
  • smart-home/events/motion

3. Alert panel

Hiển thị:

  • cảnh báo mới nhất
  • mức độ nghiêm trọng
  • nội dung cảnh báo

Dữ liệu lấy từ:

  • smart-home/alerts/security

Đây là lý do ngay từ đầu ta tách event, state và alert ra riêng.

Cách kết nối dashboard đơn giản

Bạn có thể chọn nhiều hướng:

Hướng 1: Node-RED dashboard

Rất nhanh cho demo:

  • subscribe MQTT
  • hiển thị trạng thái
  • dễ dựng prototype

Hướng 2: Web app riêng

Ví dụ:

  • backend subscribe MQTT
  • lưu DB
  • frontend hiển thị timeline và trạng thái

Hướng 3: Dashboard trong chính Raspberry Pi

Ví dụ:

  • Flask / FastAPI + WebSocket
  • hoặc web app nhẹ đọc state từ backend

Trong mini series này, điều quan trọng nhất là:

  • dữ liệu đã được đẩy ra MQTT đúng cấu trúc

Khi đó dashboard chỉ là lớp tiêu thụ dữ liệu.

Gửi Telegram / webhook thật sau này như thế nào

Dù ví dụ hiện tại mới chỉ in NOTIFY: ra terminal, cấu trúc của bạn đã đúng rồi.
Về sau chỉ cần thay nội dung trong NotificationService.

Ví dụ tư duy Telegram

class NotificationService:
    def send(self, alert):
        message = f"[{alert['level']}] {alert['message']}"
        # gọi Telegram API ở đây

Ví dụ webhook

class NotificationService:
    def send(self, alert):
        # requests.post(webhook_url, json=alert)
        pass

Nhờ vậy:

  • rule engine không cần biết Telegram là gì
  • event monitor không cần biết webhook là gì
  • toàn bộ hệ thống dễ thay đổi và bảo trì hơn

Gợi ý retained message cho state

Nếu broker hỗ trợ retained message, bạn nên cân nhắc dùng retained cho các topic state:

  • smart-home/state/door
  • smart-home/state/motion
  • smart-home/state/system

Lợi ích:

  • dashboard mới kết nối vào sẽ nhận ngay trạng thái hiện tại
  • không cần chờ event mới

Trong khi đó, event và alert thường không nên retained theo cùng cách, vì chúng là dữ liệu theo dòng thời gian.

Tổ chức project sau khi hoàn thành

Tới thời điểm này, project có thể được sắp xếp như sau:

smart_home_monitor/
├── main.py
├── sensors/
│   ├── door_sensor.py
│   └── pir_sensor.py
├── monitor/
│   └── event_monitor.py
├── rules/
│   └── rule_engine.py
├── services/
│   ├── mqtt_service.py
│   └── notification_service.py
└── logs/
    └── event_logger.py

Đây là cấu trúc rất ổn để tiếp tục mở rộng về sau.

Các lỗi thường gặp

Event có nhưng dashboard không thấy gì

Nguyên nhân:

  • publish sai topic
  • subscriber đang nghe sai topic
  • payload không đúng format JSON

Cách xử lý:

  • kiểm tra topic chính xác từng ký tự
  • log payload raw
  • test bằng mosquitto_sub

Alert gửi quá nhiều

Nguyên nhân:

  • cooldown trong rule engine quá ngắn
  • PIR quá nhạy
  • mỗi event đều bị coi là alert

Cách xử lý:

  • tăng cooldown
  • tinh chỉnh PIR
  • chỉ alert cho các event đúng điều kiện armed

Dashboard hiện sai trạng thái

Nguyên nhân:

  • state không được publish khi event đổi
  • dùng event để suy ra state nhưng logic chưa chắc chắn

Cách xử lý:

  • luôn publish state riêng sau mỗi event
  • tách state topic riêng biệt

MQTT broker đang chạy nhưng publish lỗi

Nguyên nhân:

  • sai host hoặc port
  • service connect thất bại
  • broker yêu cầu auth

Cách xử lý:

  • test kết nối broker độc lập trước
  • thêm username/password nếu cần
  • in log kết nối rõ ràng

Notify và MQTT bị dính chặt vào nhau

Nguyên nhân:

  • code viết trực tiếp tất cả trong một function lớn

Cách xử lý:

  • tách MQTTService
  • tách NotificationService
  • để main.py chỉ làm nhiệm vụ orchestration

Kết luận

Bài này là bước hoàn thiện cực kỳ quan trọng của “Hệ thống giám sát nhà thông minh” mini này.

Từ một hệ thống:

  • đọc cảm biến
  • tạo event
  • xét rule local

chúng ta đã nâng nó thành một hệ thống có thể:

  • gửi cảnh báo ra ngoài
  • publish event lên MQTT
  • đồng bộ trạng thái cho dashboard
  • sẵn sàng tích hợp vào smart home hoặc backend lớn hơn

Nói cách khác, Raspberry Pi lúc này không còn chỉ là một board đọc sensor nữa, mà đã trở thành:

👉 một trung tâm giám sát và phân phối dữ liệu cho hệ thống nhà thông minh mini

Đó cũng là đích đến đúng của chúng ta, không chỉ học cách nối dây và đọc GPIO, mà còn xây được một hệ thống nhỏ nhưng có kiến trúc đúng, có thể mở rộng thật sự.