
Raspberry Pi + Text-to-Speech: Đọc thông báo cảm biến bằng giọng nói
Đọc nhiệt độ/độ ẩm từ cảm biến DHT22 thành giọng nói tiếng Việt qua loa I2S trên Raspberry Pi 4, dùng pyttsx3/espeak-ng để tổng hợp giọng nói offline.
Dự án dựng một bộ thông báo bằng giọng nói (Text-to-Speech) trên Raspberry Pi 4: đọc định kỳ nhiệt độ và độ ẩm từ cảm biến DHT22, rồi phát ra loa dưới dạng câu thoại tiếng Việt tự nhiên ("Nhiệt độ hiện tại là 28 độ C, độ ẩm 65 phần trăm") thay vì chỉ hiện số trên màn hình. TTS chạy offline bằng engine espeak-ng/pyttsx3, không cần gọi API cloud.
Đây là mẫu ứng dụng "đảo ngược" so với các bài STT trong series: thay vì giọng nói → text (Whisper, Porcupine), bài này đi từ dữ liệu cảm biến → text → giọng nói, hoàn thiện vòng tương tác hai chiều cho một thiết bị IoT nói được.
Hướng dẫn chi tiết
Đọc nhiệt độ/độ ẩm từ DHT22 thành giọng nói tiếng Việt offline qua loa I2S trên Raspberry Pi 4 bằng pyttsx3/espeak-ng.
1. Giới thiệu
Dự án dựng một bộ thông báo bằng giọng nói (Text-to-Speech) trên Raspberry Pi 4: đọc định kỳ nhiệt độ và độ ẩm từ cảm biến DHT22, rồi phát ra loa dưới dạng câu thoại tiếng Việt tự nhiên ("Nhiệt độ hiện tại là 28 độ C, độ ẩm 65 phần trăm") thay vì chỉ hiện số trên màn hình. TTS chạy offline bằng engine espeak-ng/pyttsx3, không cần gọi API cloud.
Đây là mẫu ứng dụng "đảo ngược" so với các bài STT trong series: thay vì giọng nói → text (Whisper, Porcupine), bài này đi từ dữ liệu cảm biến → text → giọng nói, hoàn thiện vòng tương tác hai chiều cho một thiết bị IoT nói được.
2. Linh kiện cần có
Linh kiện | Số lượng | Giá tham khảo |
|---|---|---|
Raspberry Pi 4 Model B (2GB trở lên) | 1 | ~1.500.000đ |
Cảm biến nhiệt độ/độ ẩm DHT22 (AM2302) | 1 | ~65.000đ |
Loa I2S (MAX98357A + loa 3W)* | 1 | ~120.000đ |
MicroSD 32GB Class 10 | 1 | ~120.000đ |
Nguồn 5V/3A USB-C | 1 | ~80.000đ |
* Ghi chú thay thế linh kiện: hardware_catalog của IoTLabs Maker hiện chưa có module loa/DAC âm thanh nào, nên bài dùng module tự tạo "MAX98357A I2S Mono Amplifier + Loa 3W" dựa trên datasheet công khai của Adafruit MAX98357A I2S Class-D Amplifier Breakout — đây là lựa chọn phổ biến, giá rẻ cho phát âm thanh trên Raspberry Pi qua I2S (không cần DAC USB rời).
3. Sơ đồ kết nối
DHT22 | Raspberry Pi 4 |
|---|---|
VCC | 3V3 (Pin 1) |
GND | GND |
DATA | GPIO4 (Pin 7) |
Loa I2S (MAX98357A) | Raspberry Pi 4 |
|---|---|
VIN | 5V (Pin 2) |
GND | GND |
BCLK | GPIO18 / PCM_CLK (Pin 12) |
LRC | GPIO17 (Pin 11)* |
DIN | GPIO27 (Pin 13)* |
SD (enable) | 3V3 (kéo lên = luôn bật amp) |
Cùng lưu ý như các bài khác trong series: chuẩn I2S vật lý của Raspberry Pi dùng GPIO18 (BCLK), GPIO19 (LRCLK), GPIO21 (DOUT). h
Sơ đồ dùng GPIO17/GPIO27 minh hoạ logic kết nối. Khi triển khai thực tế, dùng đúng chân I2S chuẩn và bật driver âm thanh phù hợp (ví dụ dtoverlay=hifiberry-dac hoặc driver tương ứng với board MAX98357A) trong /boot/config.txt.
DHT22 nên có điện trở kéo lên 10kΩ trên chân DATA nếu dây dẫn dài hoặc tín hiệu không ổn định (theo khuyến nghị datasheet).
4. Code ví dụ #1 — Đọc cảm biến DHT22
File read_dht22.py: đọc nhiệt độ/độ ẩm, retry khi gặp lỗi checksum (hiện tượng bình thường với cảm biến 1-Wire giá rẻ).
#!/usr/bin/env python3
"""read_dht22.py - Doc nhiet do/do am tu cam bien DHT22 qua GPIO4."""
import time
try:
import adafruit_dht
import board
_DHT_AVAILABLE = True
except (ImportError, NotImplementedError):
_DHT_AVAILABLE = False
DATA_PIN_NAME = "D4" # GPIO4
def create_sensor():
if not _DHT_AVAILABLE:
return None
pin = getattr(board, DATA_PIN_NAME)
return adafruit_dht.DHT22(pin)
def read_once(sensor, retries: int = 3, delay_s: float = 2.0):
"""Doc 1 lan, tra ve (temperature_c, humidity_pct) hoac (None, None) neu loi."""
for attempt in range(retries):
try:
temperature_c = sensor.temperature
humidity_pct = sensor.humidity
if temperature_c is not None and humidity_pct is not None:
return temperature_c, humidity_pct
except RuntimeError as exc:
print(f"[Doc loi lan {attempt + 1}/{retries}]: {exc}")
time.sleep(delay_s)
return None, None
def main() -> None:
sensor = create_sensor()
if sensor is None:
print("Khong co phan cung DHT22 that (chay tren may dev) - dung du lieu mo phong.")
temperature_c, humidity_pct = 28.5, 65.0
else:
temperature_c, humidity_pct = read_once(sensor)
if temperature_c is None:
print("Khong doc duoc cam bien sau nhieu lan thu.")
return
print(f"Nhiet do: {temperature_c:.1f} C, Do am: {humidity_pct:.1f} %")
if __name__ == "__main__":
main()
5. Code ví dụ #2 — Tổng hợp giọng nói offline (pyttsx3/espeak-ng)
File speak_text.py: đọc chuỗi văn bản tiếng Việt thành giọng nói qua loa I2S. Cần cài espeak-ng ở cấp hệ thống trước (sudo apt install espeak-ng).
#!/usr/bin/env python3
"""speak_text.py - Doc van ban tieng Viet thanh giong noi offline bang pyttsx3 (espeak-ng)."""
import pyttsx3
VOICE_RATE = 150 # tu/phut, cham hon mac dinh de de nghe tieng Viet
def create_engine():
engine = pyttsx3.init()
engine.setProperty("rate", VOICE_RATE)
return engine
def speak(text: str, engine=None) -> None:
own_engine = engine is None
if engine is None:
engine = create_engine()
print(f"[TTS]: {text}")
engine.say(text)
engine.runAndWait()
if own_engine:
engine.stop()
if __name__ == "__main__":
speak("Xin chao, day la thong bao thu nghiem tu Raspberry Pi.")
6. Code ví dụ #3 — Ứng dụng hoàn chỉnh: đọc cảm biến định kỳ → phát giọng nói
File main.py: vòng lặp đọc DHT22 mỗi 60 giây, ghép câu thoại tự nhiên và phát qua loa I2S.
#!/usr/bin/env python3
"""main.py - Tich hop hoan chinh: doc DHT22 dinh ky -> tong hop cau thoai -> phat qua loa I2S."""
import signal
import sys
import time
try:
import adafruit_dht
import board
_DHT_AVAILABLE = True
except (ImportError, NotImplementedError):
_DHT_AVAILABLE = False
import pyttsx3
DATA_PIN_NAME = "D4"
READ_INTERVAL_S = 60
VOICE_RATE = 150
_running = True
def create_sensor():
if not _DHT_AVAILABLE:
return None
pin = getattr(board, DATA_PIN_NAME)
return adafruit_dht.DHT22(pin)
def read_once(sensor, retries: int = 3, delay_s: float = 2.0):
if sensor is None:
return 28.5, 65.0 # gia tri mo phong khi khong co phan cung that
for attempt in range(retries):
try:
temperature_c = sensor.temperature
humidity_pct = sensor.humidity
if temperature_c is not None and humidity_pct is not None:
return temperature_c, humidity_pct
except RuntimeError as exc:
print(f"[Doc loi lan {attempt + 1}/{retries}]: {exc}")
time.sleep(delay_s)
return None, None
def build_announcement(temperature_c: float, humidity_pct: float) -> str:
return (
f"Nhiet do hien tai la {temperature_c:.0f} do C, "
f"do am {humidity_pct:.0f} phan tram."
)
def shutdown(sig, frame):
global _running
_running = False
def main() -> None:
global _running
signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)
sensor = create_sensor()
engine = pyttsx3.init()
engine.setProperty("rate", VOICE_RATE)
print(f"San sang. Doc cam bien moi {READ_INTERVAL_S}s. Ctrl+C de dung.")
try:
while _running:
temperature_c, humidity_pct = read_once(sensor)
if temperature_c is None:
print("Khong doc duoc cam bien, bo qua chu ky nay.")
else:
text = build_announcement(temperature_c, humidity_pct)
print(f"[TTS]: {text}")
engine.say(text)
engine.runAndWait()
for _ in range(READ_INTERVAL_S):
if not _running:
break
time.sleep(1)
finally:
engine.stop()
print("\nDa dung.")
if __name__ == "__main__":
main()
sys.exit(0)
7. Lỗi thường gặp
Lỗi | Nguyên nhân | Cách sửa |
|---|---|---|
| Nhiễu tín hiệu 1-Wire, dây quá dài, hoặc đọc quá nhanh (DHT22 cần ≥2s giữa 2 lần đọc) | Thêm delay ≥2s giữa các lần đọc, dùng dây ngắn, thêm điện trở kéo lên 10kΩ trên DATA |
Không nghe thấy âm thanh dù code chạy không lỗi | ALSA chưa nhận loa I2S làm default output | Kiểm tra |
Giọng đọc tiếng Việt nghe như tiếng Anh/lạ | espeak-ng không có gói ngôn ngữ tiếng Việt hoặc pyttsx3 chọn sai voice | Kiểm tra |
| Thư viện CircuitPython chưa cài đúng nền tảng Raspberry Pi | Cài qua |
Chương trình treo lâu ở | Driver TTS chờ audio device không phản hồi (device bận/không tồn tại) | Kiểm tra lại cấu hình ALSA, test phát âm thanh bằng |
8. Tổng kết
Dự án đã dựng một bộ thông báo giọng nói hoàn chỉnh: đọc cảm biến định kỳ, ghép câu thoại tự nhiên, và phát qua loa I2S — tất cả chạy offline không phụ thuộc cloud. Mẫu thiết kế "đọc dữ liệu → nói ra" này áp dụng được cho bất kỳ loại thông báo IoT nào (cảnh báo ngưỡng, trạng thái thiết bị, lời chào khi khởi động) chỉ bằng cách thay đổi hàm build_announcement().
Lưu ý về độ chính xác: độ chính xác nhiệt độ/độ ẩm (±0.5°C, ±2-5% RH) lấy từ datasheet công khai của DHT22 — đây là thông số danh định của nhà sản xuất trong điều kiện chuẩn, không phải số đo thực tế đã kiểm chứng trên board cụ thể của bạn.
Chất lượng giọng đọc TTS offline (espeak-ng) không tự nhiên bằng dịch vụ TTS cloud trả phí — đây là đánh đổi hợp lý để có giải pháp hoàn toàn offline.