Raspberry Pi + Text-to-Speech: Đọc thông báo cảm biến bằng giọng nói
Trung cấp1/8/2026- Tác giả: IoTlabs Maker

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.

Raspberry PiText-to-SpeechTTSDHT22PythonVoiceOffline

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

RuntimeError: Checksum did not validate

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 aplay -l, cấu hình đúng dtoverlay cho DAC I2S, đặt loa I2S làm default trong ~/.asoundrc

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 espeak-ng --voices=vi, chọn đúng voice tiếng Việt qua engine.setProperty('voice', ...)

ModuleNotFoundError: No module named 'board'

Thư viện CircuitPython chưa cài đúng nền tảng Raspberry Pi

Cài qua pip install adafruit-circuitpython-dhtpip install RPI.GPIO adafruit-blinka theo hướng dẫn Adafruit cho Raspberry Pi

Chương trình treo lâu ở engine.runAndWait()

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 speaker-test trước khi chạy code TTS

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.