Ở bài 23.4, chúng ta đã điều khiển được đèn, quạt và relay ngay trên Raspberry Pi thông qua GPIO. Đó là một bước rất quan trọng vì hệ thống đã có thể:
- nghe giọng nói
- nhận diện lệnh
- map sang command
- thực thi hành động thật
Tuy nhiên, trong một hệ thống IoT thực tế, Raspberry Pi không phải lúc nào cũng là nơi trực tiếp bật thiết bị. Nhiều trường hợp, Raspberry Pi chỉ đóng vai trò như bộ điều phối lệnh bằng giọng nói, sau đó gửi lệnh sang:
- ESP32
- gateway khác
- dashboard
- hệ thống automation
- thiết bị ở phòng khác
Đây chính là lúc MQTT trở thành mảnh ghép rất phù hợp.
Trong bài này, chúng ta sẽ nâng cấp Voice Assistant offline tiếng Việt trên Raspberry Pi từ mô hình điều khiển local sang mô hình gửi lệnh qua MQTT để điều khiển hệ thống IoT.
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ể:
- nhận diện lệnh giọng nói tiếng Việt bằng Vosk
- chuyển câu lệnh thành command nội bộ
- publish command đó lên MQTT broker
- để ESP32 hoặc thiết bị IoT khác subscribe và thực thi
Nói ngắn gọn:
Bạn nói trên Raspberry Pi, nhưng thiết bị ở nơi khác vẫn có thể hành động.
Vì sao nên gửi lệnh qua MQTT thay vì chỉ điều khiển local
Điều khiển GPIO trực tiếp rất tốt cho demo, nhưng nếu muốn đi xa hơn, bạn sẽ sớm gặp các nhu cầu như:
- đèn và quạt nằm trên ESP32 chứ không nằm trên Pi
- một câu lệnh phải điều khiển nhiều thiết bị
- cần lưu log command
- cần hiển thị trạng thái lên dashboard
- cần mở rộng sang smart home, farm, lab hoặc gateway nhiều node
Nếu mọi thứ chỉ chạy local trong một file Python thì hệ thống sẽ nhanh chóng khó mở rộng.
MQTT giúp tách hệ thống thành các phần rõ ràng
Ví dụ:
- Raspberry Pi lo phần nghe và hiểu lệnh
- ESP32 lo phần điều khiển relay
- dashboard lo phần hiển thị trạng thái
- backend lo phần ghi log / automation
Khi đó, Raspberry Pi chỉ cần publish một message như:
{
"command": "LIGHT_ON"
}
Bên nào quan tâm thì subscribe topic tương ứng và xử lý.
Đây là tư duy rất hợp với IoT: tách nhận lệnh, truyền lệnh và thực thi lệnh thành các thành phần riêng.
Kiến trúc tổng thể
Luồng xử lý của hệ thống trong bài này sẽ là:
Micro -> Vosk -> Text -> Command Parser -> MQTT Publish -> ESP32 / Device / Dashboard
Ví dụ thực tế:
“bật đèn phòng khách” -> LIGHT_ON -> publish MQTT -> ESP32 nhận lệnh -> relay bật
Nếu nhìn theo vai trò từng thành phần:
- Raspberry Pi: Voice Assistant offline
- MQTT Broker: trung gian truyền lệnh
- ESP32 / thiết bị IoT: nơi thực thi
- Dashboard: nơi theo dõi command hoặc trạng thái
Chuẩn bị trước khi làm
Bạn nên có sẵn:
- Raspberry Pi đã cài micro và test audio input
- project Vosk từ bài 23.3
- command parser từ bài 23.4
- một MQTT broker để test
- một MQTT client khác để quan sát message
Có thể dùng broker nào
Bạn có thể dùng:
- Mosquitto cài local trên Raspberry Pi
- broker trong mạng LAN
- broker cloud
- hoặc broker MQTT riêng của bạn
Nếu chỉ muốn test nhanh, bạn có thể dùng một broker local để nhìn rõ luồng command.
Thiết kế topic MQTT cho voice command
Đây là phần rất quan trọng.
Nếu topic thiết kế quá sơ sài, sau này rất khó mở rộng. Vì vậy, ngay từ đầu ta nên đặt topic đủ rõ nghĩa.
Cách đơn giản để demo
home/voice/command
Message ví dụ:
{
"command": "LIGHT_ON"
}
Cách tốt hơn để dễ mở rộng
iotlabs/home/voice/commands
Hoặc nếu muốn điều khiển theo khu vực:
iotlabs/home/livingroom/commands
iotlabs/home/bedroom/commands
iotlabs/farm/gateway01/commands
Nếu muốn gửi rõ hơn theo thiết bị
iotlabs/home/light01/set
iotlabs/home/fan01/set
Trong bài này, để dễ hiểu, chúng ta sẽ dùng topic trung gian:
iotlabs/voice/commands
Thiết kế payload command
Payload không nên chỉ là một chuỗi text đơn giản, vì sau này bạn sẽ muốn thêm:
- timestamp
- nguồn gửi
- target device
- trạng thái
- id của command
Payload tối thiểu
{
"command": "LIGHT_ON"
}
Payload nên dùng hơn
{
"source": "rpi-voice-assistant",
"command": "LIGHT_ON",
"target": "light01",
"ts": 1712800000
}
Ví dụ với quạt
{
"source": "rpi-voice-assistant",
"command": "FAN_OFF",
"target": "fan01",
"ts": 1712800050
}
Thiết kế kiểu này giúp hệ thống có thể debug và mở rộng dễ hơn nhiều.
Cài thư viện MQTT cho Python
Chúng ta sẽ dùng thư viện Paho MQTT.
Cài bằng lệnh:
pip install paho-mqtt
Tạo file publish command MQTT
Tạo file:
mqtt_publisher.py
Code mẫu publish MQTT
import json
import time
import paho.mqtt.client as mqtt
BROKER_HOST = "127.0.0.1"
BROKER_PORT = 1883
TOPIC = "iotlabs/voice/commands"
client = mqtt.Client()
def connect_mqtt():
client.connect(BROKER_HOST, BROKER_PORT, 60)
def publish_command(command, target="device01"):
payload = {
"source": "rpi-voice-assistant",
"command": command,
"target": target,
"ts": int(time.time())
}
client.publish(TOPIC, json.dumps(payload))
print("Published:", payload)
if __name__ == "__main__":
connect_mqtt()
publish_command("LIGHT_ON", "light01")
Test publish trước khi tích hợp voice
Chạy thử:
python mqtt_publisher.py
Nếu broker đang hoạt động, bạn sẽ thấy log:
Published: {'source': 'rpi-voice-assistant', 'command': 'LIGHT_ON', 'target': 'light01', 'ts': 1712800000}
Dùng MQTT subscriber để kiểm tra
Ở terminal khác, subscribe thử topic:
mosquitto_sub -h 127.0.0.1 -t iotlabs/voice/commands
Kết quả mong đợi:
{"source": "rpi-voice-assistant", "command": "LIGHT_ON", "target": "light01", "ts": 1712800000}
Nếu đã thấy message này, nghĩa là phần publish MQTT của bạn đã ổn.
Tích hợp MQTT vào Voice Assistant
Giờ chúng ta sẽ kết nối phần nhận diện lệnh với phần publish MQTT.
Ở bài 23.4, bạn đã có logic kiểu như:
def parse_command(text):
if "bat den" in text:
return "LIGHT_ON"
elif "tat den" in text:
return "LIGHT_OFF"
elif "bat quat" in text:
return "FAN_ON"
elif "tat quat" in text:
return "FAN_OFF"
return None
Bây giờ, thay vì gọi GPIO local ngay lập tức, ta sẽ publish command lên MQTT.
Hàm publish command
import json
import time
import paho.mqtt.client as mqtt
BROKER_HOST = "127.0.0.1"
BROKER_PORT = 1883
TOPIC = "iotlabs/voice/commands"
mqtt_client = mqtt.Client()
mqtt_client.connect(BROKER_HOST, BROKER_PORT, 60)
def send_command_mqtt(command, target):
payload = {
"source": "rpi-voice-assistant",
"command": command,
"target": target,
"ts": int(time.time())
}
mqtt_client.publish(TOPIC, json.dumps(payload))
print("MQTT sent:", payload)
Map command sang target device
Ví dụ bạn muốn:
LIGHT_ONgửi đếnlight01FAN_ONgửi đếnfan01
Ta có thể viết:
def resolve_target(command):
if command in ["LIGHT_ON", "LIGHT_OFF"]:
return "light01"
elif command in ["FAN_ON", "FAN_OFF"]:
return "fan01"
return "unknown"
Tích hợp vào loop chính
Trong luồng nhận diện Vosk:
while True:
data = q.get()
if recognizer.AcceptWaveform(data):
result = json.loads(recognizer.Result())
text = normalize(result.get("text", ""))
print("Bạn nói:", text)
command = parse_command(text)
if command:
target = resolve_target(command)
send_command_mqtt(command, target)
Demo kết quả
Khi bạn nói:
bật đèn
Terminal trên Raspberry Pi:
Bạn nói: bat den
MQTT sent: {'source': 'rpi-voice-assistant', 'command': 'LIGHT_ON', 'target': 'light01', 'ts': 1712800000}
Terminal subscribe:
{"source": "rpi-voice-assistant", "command": "LIGHT_ON", "target": "light01", "ts": 1712800000}
Từ đây, bất kỳ ESP32 hay service nào subscribe topic phù hợp đều có thể nhận lệnh và xử lý.
Viết một subscriber Python đơn giản để mô phỏng thiết bị nhận lệnh
Để test nhanh mà chưa cần ESP32, bạn có thể viết một subscriber Python.
Tạo file:
device_simulator.py
Code mẫu
import json
import paho.mqtt.client as mqtt
BROKER_HOST = "127.0.0.1"
BROKER_PORT = 1883
TOPIC = "iotlabs/voice/commands"
def on_connect(client, userdata, flags, rc):
print("Connected to broker")
client.subscribe(TOPIC)
def on_message(client, userdata, msg):
payload = json.loads(msg.payload.decode())
print("Received:", payload)
command = payload.get("command")
target = payload.get("target")
if target == "light01":
if command == "LIGHT_ON":
print("Simulate: Light ON")
elif command == "LIGHT_OFF":
print("Simulate: Light OFF")
elif target == "fan01":
if command == "FAN_ON":
print("Simulate: Fan ON")
elif command == "FAN_OFF":
print("Simulate: Fan OFF")
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER_HOST, BROKER_PORT, 60)
client.loop_forever()
Kết quả mô phỏng
Khi Raspberry Pi gửi lệnh:
bật quạt
Subscriber sẽ nhận được:
Received: {'source': 'rpi-voice-assistant', 'command': 'FAN_ON', 'target': 'fan01', 'ts': 1712800200}
Simulate: Fan ON
Đây chính là nguyên lý cơ bản của một hệ thống IoT command-driven.
Cách mở rộng để điều khiển ESP32 thật
Nếu muốn điều khiển ESP32 thật, bạn chỉ cần để ESP32 subscribe đúng topic và parse payload JSON.
Ví dụ logic trên ESP32 sẽ là:
- subscribe topic
iotlabs/voice/commands - parse field
target - nếu target đúng với device của nó thì thực thi relay
- nếu không đúng thì bỏ qua
Ví dụ:
- ESP32 ở phòng khách nhận
light01 - ESP32 ở bàn làm việc nhận
fan01
Nhờ vậy, một Raspberry Pi duy nhất có thể gửi lệnh đến nhiều node IoT khác nhau.
Nên dùng 1 topic chung hay nhiều topic riêng
Có hai cách phổ biến.
Cách 1: Một topic chung
iotlabs/voice/commands
Ưu điểm:
- đơn giản
- dễ test
- dễ bắt đầu
Nhược điểm:
- thiết bị nào cũng phải đọc rồi lọc
target
Cách 2: Topic riêng theo thiết bị
iotlabs/devices/light01/set
iotlabs/devices/fan01/set
Ưu điểm:
- rõ ràng
- thiết bị subscribe đúng topic của nó
- mở rộng sạch hơn
Nhược điểm:
- bên publish phải map topic kỹ hơn
Gợi ý cho giai đoạn học
- demo nhanh: dùng 1 topic chung
- hệ thống lớn hơn: chuyển sang topic riêng theo thiết bị
Thêm phản hồi trạng thái để hệ thống hoàn chỉnh hơn
Một điểm rất hay là sau khi nhận command, thiết bị có thể publish trạng thái ngược lại.
Ví dụ:
- Raspberry Pi gửi:
LIGHT_ON - ESP32 bật relay xong
- ESP32 publish lên topic trạng thái:
iotlabs/devices/light01/status
Payload:
{
"device": "light01",
"status": "on",
"ts": 1712800300
}
Nhờ đó:
- dashboard biết đèn đang bật
- backend có thể log lại
- Voice Assistant có thể đọc phản hồi sau này
Các lỗi thường gặp
Không publish được MQTT
Nguyên nhân:
- broker chưa chạy
- sai host / port
- firewall chặn
- client chưa connect
Cách xử lý:
- test broker trước bằng
mosquitto_sub - kiểm tra host, port
- in log khi connect
Publish thành công nhưng thiết bị không nhận
Nguyên nhân:
- subscribe sai topic
- payload không đúng format
- target không khớp
Cách xử lý:
- in payload raw ở bên subscriber
- xác nhận topic hoàn toàn giống nhau
- kiểm tra lại field
target
Voice nhận đúng nhưng không có command
Nguyên nhân:
- parse command chưa đủ các biến thể
- text Vosk trả về khác mong đợi
Cách xử lý:
- in
textra terminal - bổ sung mapping như
mo den,cho den sang,bat quat
Thiết bị nhận được command nhưng không đổi trạng thái
Nguyên nhân:
- logic relay sai
- GPIO đảo mức HIGH/LOW
- code subscriber chưa thực thi đúng
Cách xử lý:
- test relay local trước
- log từng bước xử lý command
- xác minh target đúng với device
Cách tổ chức project cho sạch hơn
Khi tới bài này, project đã bắt đầu có nhiều phần. Bạn nên tách code ra:
voice_assistant/
├── main.py
├── voice/
│ ├── recognizer.py
│ └── audio_input.py
├── commands/
│ ├── parser.py
│ └── mapper.py
├── mqtt/
│ └── publisher.py
└── devices/
└── gpio_controller.py
Cách này giúp sau này bạn dễ:
- mở rộng thêm command
- đổi broker
- thay từ GPIO local sang MQTT
- kết nối dashboard hoặc backend
Hoàn thiện mini series Voice Assistant offline tiếng Việt với Raspberry Pi.
Đến đây, bạn đã có một hệ thống khá hoàn chỉnh:
- Bài 23.1: Tổng quan hệ thống điều khiển giọng nói offline
- Bài 23.2: Cài micro, kiểm tra audio input, thu âm và test luồng âm thanh local trên Raspberry Pi.
- Bài 23.3: Cài model Vosk, nhận diện các câu lệnh tiếng Việt với bộ từ vựng giới hạn, và map câu lệnh sang command nội bộ.
- Bài 23.4: Điều khiển thiết bị thật bằng giọng nói: LED, relay, quạt hoặc thiết bị mô phỏng.
- Bài 23.5: Gửi lệnh qua MQTT để điều khiển một hệ thống IoT hoàn chỉnh hơn, ví dụ ESP32 hoặc dashboard.
Nghĩa là bây giờ hệ thống của bạn đã đi được trọn một vòng:
Nghe giọng nói -> hiểu lệnh -> phát lệnh -> điều khiển thiết bị IoT
Kết luận
Bài này là bước nâng cấp rất quan trọng vì nó biến Voice Assistant trên Raspberry Pi từ một demo điều khiển local thành một trung tâm điều phối lệnh giọng nói cho hệ thống IoT.
Thay vì chỉ bật đèn gắn trực tiếp vào Raspberry Pi, giờ đây bạn có thể:
- điều khiển ESP32 ở xa
- gửi lệnh đến nhiều thiết bị
- tích hợp dashboard
- ghi log command
- xây dựng smart home hoặc lab mini theo hướng mở rộng thật sự
Đây cũng là điểm mà MQTT phát huy thế mạnh rõ ràng nhất: tách rời phần nhận lệnh và phần thực thi, giúp hệ thống vừa gọn, vừa linh hoạt, vừa đúng tư duy IoT.


