
Raspberry Pi + Google Assistant SDK: Trợ lý giọng nói cơ bản
Dựng trợ lý giọng nói cơ bản trên Raspberry Pi 4 bằng Google Assistant SDK (gRPC), hỏi đáp bằng giọng nói qua mic và loa I2S, kích hoạt bằng nút bấm.
Dự án hướng dẫn dựng một trợ lý giọng nói cơ bản trên Raspberry Pi 4 dùng Google Assistant Library / SDK (qua gRPC): nhấn nút để bắt đầu hỏi, Google Assistant xử lý câu hỏi trên cloud và trả lời bằng giọng nói qua loa I2S.
Khác với các bài offline khác trong series (Whisper, Porcupine, TTS nội bộ), dự án này minh hoạ cách tích hợp dịch vụ trợ lý giọng nói thương mại đầy đủ tính năng (kiến thức tổng quát, thời tiết, lịch, nhắc việc...) — đánh đổi lại là cần kết nối Internet và tài khoản Google Cloud.
Bài phù hợp để so sánh trực tiếp ưu/nhược điểm giữa giải pháp tự xây (offline, riêng tư, giới hạn tính năng) và giải pháp thương mại (online, nhiều tính năng, phụ thuộc dịch vụ bên thứ ba) khi thiết kế sản phẩm IoT thực tế.
Hướng dẫn chi tiết
Dựng trợ lý giọng nói cơ bản trên Raspberry Pi 4 bằng Google Assistant SDK, hỏi đáp qua mic và loa I2S, kích hoạt bằng nút bấm.
1. Giới thiệu
Dự án hướng dẫn dựng một trợ lý giọng nói cơ bản trên Raspberry Pi 4 dùng Google Assistant Library / SDK (qua gRPC): nhấn nút để bắt đầu hỏi, Google Assistant xử lý câu hỏi trên cloud và trả lời bằng giọng nói qua loa I2S.
Khác với các bài offline khác trong series (Whisper, Porcupine, TTS nội bộ), dự án này minh hoạ cách tích hợp dịch vụ trợ lý giọng nói thương mại đầy đủ tính năng (kiến thức tổng quát, thời tiết, lịch, nhắc việc...) — đánh đổi lại là cần kết nối Internet và tài khoản Google Cloud.
Bài phù hợp để so sánh trực tiếp ưu/nhược điểm giữa giải pháp tự xây (offline, riêng tư, giới hạn tính năng) và giải pháp thương mại (online, nhiều tính năng, phụ thuộc dịch vụ bên thứ ba) khi thiết kế sản phẩm IoT thực tế.
2. Linh kiện cần có
Linh kiện | Số lượng | Giá tham khảo |
|---|---|---|
Raspberry Pi 4 Model B (4GB trở lên) | 1 | ~1.800.000đ |
Mic I2S MEMS INMP441 | 1 | ~90.000đ |
Loa I2S (MAX98357A + loa 3W)* | 1 | ~120.000đ |
Nút nhấn (push-to-talk) | 1 | ~2.000đ |
MicroSD 32GB Class 10 | 1 | ~120.000đ |
Nguồn 5V/3A USB-C | 1 | ~80.000đ |
Ghi chú thay thế linh kiện: cùng module loa tự tạo "MAX98357A I2S Mono Amplifier + Loa 3W" đã dùng ở bài Text-to-Speech trong series, vì hardware_catalog chưa có module loa/DAC âm thanh nào.
Yêu cầu tài khoản: cần tài khoản Google Cloud (có bật Google Assistant API), thiết lập OAuth credentials qua google-oauthlib-tool theo hướng dẫn chính thức của Google trước khi chạy được code trong bài. Không có sẵn tài khoản demo miễn phí dùng chung.
3. Sơ đồ kết nối
INMP441 (Mic I2S) | Raspberry Pi 4 |
|---|---|
VCC | 3V3 (Pin 1) |
GND | GND |
SCK | GPIO18 / PCM_CLK (Pin 12) — chia sẻ BCLK với loa |
WS | GPIO17 (Pin 11) — chia sẻ LRC với loa* |
SD | GPIO27 (Pin 13) |
L/R | GND |
Loa I2S (MAX98357A) | Raspberry Pi 4 |
|---|---|
VIN | 5V (Pin 2) |
GND | GND |
BCLK | GPIO18 (chia sẻ với mic SCK) |
LRC | GPIO17 (chia sẻ với mic WS) |
DIN | GPIO22 (Pin 15) |
SD (enable) | 3V3 (kéo lên = luôn bật amp) |
Nút nhấn | Raspberry Pi 4 |
|---|---|
Chân A | GPIO4 (Pin 7), INPUT_PULLUP |
Chân B | GND |
Thiết kế chia sẻ BCLK/LRC giữa mic và loa phản ánh đúng kiến trúc I2S full-duplex thực tế của Raspberry Pi: một bus I2S vật lý (GPIO18=BCLK, GPIO19=LRCLK chuẩn) dùng chung xung clock/word-select cho cả luồng ghi (mic → GPIO20/DIN) và luồng phát (→ loa qua GPIO21/DOUT), chỉ khác đường dữ liệu.
Sơ đồ minh hoạ dùng GPIO17 làm WS/LRC chung và GPIO27/GPIO22 làm 2 đường dữ liệu riêng (SD của mic, DIN của loa) — khi triển khai thực tế, dùng đúng bộ chân I2S chuẩn (GPIO18/19/20/21) và cấu hình ALSA cho thiết bị audio full-duplex.
4. Code ví dụ #1 — Nút push-to-talk kích hoạt truy vấn
File button_trigger.py: chờ nhấn nút GPIO4 để bắt đầu một lượt hỏi trợ lý (thay cho việc dùng wake word liên tục — đơn giản hơn cho bản demo cơ bản).
#!/usr/bin/env python3
"""button_trigger.py - Cho nhan nut push-to-talk qua GPIO4 de kich hoat truy van tro ly."""
import time
try:
import RPi.GPIO as GPIO
except ImportError:
GPIO = None
BUTTON_PIN = 4
def gpio_init() -> None:
if GPIO is None:
return
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def button_pressed() -> bool:
if GPIO is None:
return False
return GPIO.input(BUTTON_PIN) == GPIO.LOW
def wait_for_press(poll_interval_s: float = 0.02) -> None:
"""Blocking: doi den khi nut duoc nhan."""
print("Cham nut de bat dau hoi tro ly...")
while not button_pressed():
time.sleep(poll_interval_s)
print("Da nhan nut - bat dau ghi am cau hoi.")
def cleanup() -> None:
if GPIO is not None:
GPIO.cleanup()
if __name__ == "__main__":
gpio_init()
try:
wait_for_press()
finally:
cleanup()
5. Code ví dụ #2 — Gọi Google Assistant SDK
File assistant_query.py: kiểm tra credentials và minh hoạ luồng gửi câu hỏi tới Google Assistant. Phần gọi gRPC audio-stream thật được rút gọn thành text-in/text-out để ví dụ chạy được mà không cần kết nối Google Cloud khi đọc bài — khi triển khai thật, thay bằng lời gọi google.assistant.embedded.v1alpha2 streaming audio.
#!/usr/bin/env python3
"""assistant_query.py - Goi Google Assistant SDK (gRPC) de gui 1 cau hoi bang giong noi."""
import json
import os
from pathlib import Path
CREDENTIALS_PATH = Path.home() / ".config" / "google-oauthlib-tool" / "credentials.json"
DEVICE_CONFIG_PATH = Path.home() / ".config" / "googlesamples-assistant" / "device_config.json"
def check_credentials() -> bool:
"""Kiem tra credentials da duoc thiet lap truoc khi goi Assistant SDK."""
if not CREDENTIALS_PATH.exists():
print(f"Thieu credentials tai {CREDENTIALS_PATH}. Chay google-oauthlib-tool truoc.")
return False
if not DEVICE_CONFIG_PATH.exists():
print(f"Thieu device config tai {DEVICE_CONFIG_PATH}. Dang ky device truoc.")
return False
return True
def load_device_config() -> dict:
with open(DEVICE_CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)
def send_text_query(text: str) -> str:
"""Mo phong goi Assistant SDK bang van ban (thay cho audio stream that)."""
if not check_credentials():
return "Loi: chua thiet lap Google Assistant credentials."
print(f"[Gui cau hoi toi Google Assistant]: {text}")
return f"(Mo phong) Google Assistant da nhan cau hoi: '{text}'"
if __name__ == "__main__":
response = send_text_query("Hom nay troi the nao?")
print(f"[Phan hoi]: {response}")
6. Code ví dụ #3 — Ứng dụng hoàn chỉnh: nút → hỏi trợ lý → phát trả lời
File main.py: vòng lặp hoàn chỉnh — nhấn nút, gửi câu hỏi, nhận trả lời và đọc qua loa I2S bằng TTS nội bộ.
#!/usr/bin/env python3
"""main.py - Tich hop hoan chinh: nut push-to-talk -> goi Google Assistant SDK -> phat tra loi."""
import json
import signal
import sys
import time
from pathlib import Path
try:
import RPi.GPIO as GPIO
except ImportError:
GPIO = None
import pyttsx3
BUTTON_PIN = 4
CREDENTIALS_PATH = Path.home() / ".config" / "google-oauthlib-tool" / "credentials.json"
DEVICE_CONFIG_PATH = Path.home() / ".config" / "googlesamples-assistant" / "device_config.json"
_running = True
def gpio_init() -> None:
if GPIO is None:
return
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def button_pressed() -> bool:
if GPIO is None:
return False
return GPIO.input(BUTTON_PIN) == GPIO.LOW
def check_credentials() -> bool:
if not CREDENTIALS_PATH.exists():
print(f"Thieu credentials tai {CREDENTIALS_PATH}. Chay google-oauthlib-tool truoc.")
return False
if not DEVICE_CONFIG_PATH.exists():
print(f"Thieu device config tai {DEVICE_CONFIG_PATH}. Dang ky device truoc.")
return False
return True
def record_query_placeholder() -> str:
"""Placeholder ghi am + STT qua Google Assistant audio stream."""
return "Hom nay troi the nao?"
def query_assistant(text: str) -> str:
if not check_credentials():
return "Xin loi, chua the ket noi Google Assistant."
print(f"[Gui cau hoi]: {text}")
return f"(Mo phong) Day la cau tra loi cho: {text}"
def speak(engine, text: str) -> None:
print(f"[TTS]: {text}")
engine.say(text)
engine.runAndWait()
def shutdown(sig, frame):
global _running
_running = False
def main() -> None:
global _running
gpio_init()
signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)
engine = pyttsx3.init()
print("San sang. Cham nut de hoi tro ly. Ctrl+C de dung.")
try:
while _running:
while not button_pressed() and _running:
time.sleep(0.02)
if not _running:
break
query_text = record_query_placeholder()
answer = query_assistant(query_text)
speak(engine, answer)
time.sleep(1)
finally:
engine.stop()
if GPIO is not None:
GPIO.cleanup()
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 |
|---|---|---|
Thiếu credentials tại | Chưa chạy bước OAuth setup | Chạy |
| Chưa bật Google Assistant API trong Google Cloud Console cho project | Vào Google Cloud Console → APIs & Services → bật "Google Assistant API" |
Không nghe/ghi được âm thanh dù credentials đúng | Cấu hình ALSA full-duplex (mic + loa cùng lúc) chưa đúng | Kiểm tra |
Trợ lý phản hồi chậm hoặc timeout | Kết nối Internet không ổn định — dịch vụ Google Assistant cần mạng liên tục | Kiểm tra kết nối mạng, đây là đánh đổi cố hữu khi dùng dịch vụ cloud thay vì giải pháp offline |
| Chưa cài đúng gói SDK | Cài theo đúng hướng dẫn chính thức: |
8. Tổng kết
Dự án đã dựng khung trợ lý giọng nói cơ bản tích hợp Google Assistant SDK: nút kích hoạt, gửi truy vấn, nhận và phát trả lời qua loa I2S. So với các bài offline khác trong series, cách tiếp cận này đánh đổi sự riêng tư/độc lập mạng để lấy tính năng đầy đủ và chất lượng giọng nói cao hơn, lựa chọn phù hợp phụ thuộc vào yêu cầu sản phẩm cụ thể (thiết bị dùng nơi không có mạng nên chọn hướng offline như Whisper/Porcupine; thiết bị cần trả lời kiến thức tổng quát nên cân nhắc dịch vụ cloud).
Lưu ý quan trọng: phần gọi Google Assistant SDK trong bài là khung sườn minh hoạ (text placeholder thay cho audio stream gRPC thật) để giữ code mẫu độc lập, compile được và không yêu cầu tài khoản Google Cloud khi đọc bài.
Khi triển khai thực tế, cần thay query_assistant()/record_query_placeholder() bằng lời gọi gRPC streaming audio thật theo tài liệu chính thức của Google, và tự kiểm thử trên phần cứng thật trước khi coi là hoàn chỉnh.