Reachy Mini Home Assistant Voice Assistant - 项目计划
项目概述
将 Home Assistant 语音助手功能集成到 Reachy Mini 机器人,通过 ESPHome 协议与 Home Assistant 通信。
本地项目目录参考 (禁止修改参考目录内任何文件)
核心设计原则
- 零配置安装 - 用户只需安装应用,无需手动配置
- 使用 Reachy Mini 原生硬件 - 使用机器人自带的麦克风和扬声器
- Home Assistant 集中管理 - 所有配置在 Home Assistant 端完成
- 运动反馈 - 语音交互时提供头部运动和天线动画反馈
技术架构
┌─────────────────────────────────────────────────────────────┐
│ Reachy Mini │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Microphone │→ │ Wake Word │→ │ ESPHome Protocol │ │
│ │ (ReSpeaker) │ │ Detection │ │ Server (Port 6053) │ │
│ └─────────────┘ └─────────────┘ └──────────┬──────────┘ │
│ │ │
│ ┌─────────────┐ ┌─────────────┐ │ │
│ │ Speaker │← │ Audio │←────────────┘ │
│ │ (ReSpeaker) │ │ Player │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Motion Controller (Head + Antennas) │ │
│ │ - on_wakeup: 点头确认 │ │
│ │ - on_listening: 注视用户 │ │
│ │ - on_thinking: 抬头思考 │ │
│ │ - on_speaking: 说话时微动 │ │
│ │ - on_idle: 返回中立位置 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
│ ESPHome Protocol
▼
┌─────────────────────────────────────────────────────────────┐
│ Home Assistant │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ STT Engine │ │ Intent │ │ TTS Engine │ │
│ │ │ │ Processing │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
已完成功能
核心功能
- ESPHome 协议服务器实现
- mDNS 服务发现(自动被 Home Assistant 发现)
- 本地唤醒词检测(microWakeWord)
- 音频流传输到 Home Assistant
- TTS 音频播放
- 停止词检测
Reachy Mini 集成
- 使用 Reachy Mini SDK 的麦克风输入
- 使用 Reachy Mini SDK 的扬声器输出
- 头部运动控制(点头、摇头、注视)
- 天线动画控制
- 语音状态反馈动作
应用架构
- 符合 Reachy Mini App 架构
- 自动下载唤醒词模型
- 自动下载音效文件
- 无需 .env 配置文件
文件清单
reachy_mini_ha_voice/
├── reachy_mini_ha_voice/
│ ├── __init__.py # 包初始化
│ ├── __main__.py # 命令行入口
│ ├── main.py # ReachyMiniApp 入口
│ ├── voice_assistant.py # 语音助手服务
│ ├── satellite.py # ESPHome 协议处理
│ ├── audio_player.py # 音频播放器
│ ├── camera_server.py # MJPEG 摄像头流服务器
│ ├── motion.py # 运动控制
│ ├── models.py # 数据模型
│ ├── entity.py # ESPHome 基础实体
│ ├── entity_extensions.py # 扩展实体类型
│ ├── reachy_controller.py # Reachy Mini 控制器包装
│ ├── api_server.py # API 服务器
│ ├── zeroconf.py # mDNS 发现
│ └── util.py # 工具函数
├── wakewords/ # 唤醒词模型(自动下载)
│ ├── okay_nabu.json
│ ├── okay_nabu.tflite
│ ├── hey_jarvis.json
│ ├── hey_jarvis.tflite
│ ├── stop.json
│ └── stop.tflite
├── sounds/ # 音效文件(自动下载)
│ ├── wake_word_triggered.flac
│ └── timer_finished.flac
├── pyproject.toml # 项目配置
├── README.md # 说明文档
└── PROJECT_PLAN.md # 项目计划
依赖项
dependencies = [
"reachy-mini", # Reachy Mini SDK
"sounddevice>=0.4.6", # 音频处理(备用)
"soundfile>=0.12.0", # 音频文件读取
"numpy>=1.24.0", # 数值计算
"pymicro-wakeword>=2.0.0,<3.0.0", # 唤醒词检测
"pyopen-wakeword>=1.0.0,<2.0.0", # 备用唤醒词
"aioesphomeapi>=42.0.0", # ESPHome 协议
"zeroconf>=0.100.0", # mDNS 发现
"scipy>=1.10.0", # 运动控制
"pydantic>=2.0.0", # 数据验证
]
使用流程
安装应用
- 从 Reachy Mini App Store 安装
- 或
pip install reachy-mini-ha-voice
启动应用
- 应用自动启动 ESPHome 服务器(端口 6053)
- 自动下载所需模型和音效
连接 Home Assistant
- Home Assistant 自动发现设备(mDNS)
- 或手动添加:设置 → 设备与服务 → 添加集成 → ESPHome
使用语音助手
- 说 "Okay Nabu" 唤醒
- 说出命令
- Reachy Mini 会做出运动反馈
ESPHome 实体规划
基于 Reachy Mini SDK 深入分析,以下实体已暴露给 Home Assistant:
已实现实体
| 实体类型 | 名称 | 说明 |
|---|---|---|
| Media Player | media_player |
音频播放控制 |
| Voice Assistant | voice_assistant |
语音助手管道 |
已实现的控制实体 (Controls) - 可读写
Phase 1-3: 基础控制与姿态
| ESPHome 实体类型 | 名称 | SDK API | 范围/选项 | 说明 |
|---|---|---|---|---|
Number |
speaker_volume |
AudioPlayer.set_volume() |
0-100 | 扬声器音量 |
Select |
motor_mode |
set_motor_control_mode() |
enabled/disabled/gravity_compensation | 电机模式选择 |
Switch |
motors_enabled |
enable_motors() / disable_motors() |
on/off | 电机扭矩开关 |
Button |
wake_up |
mini.wake_up() |
- | 唤醒机器人动作 |
Button |
go_to_sleep |
mini.goto_sleep() |
- | 睡眠机器人动作 |
Number |
head_x |
goto_target(head=...) |
±50mm | 头部 X 位置控制 |
Number |
head_y |
goto_target(head=...) |
±50mm | 头部 Y 位置控制 |
Number |
head_z |
goto_target(head=...) |
±50mm | 头部 Z 位置控制 |
Number |
head_roll |
goto_target(head=...) |
-40° ~ +40° | 头部翻滚角控制 |
Number |
head_pitch |
goto_target(head=...) |
-40° ~ +40° | 头部俯仰角控制 |
Number |
head_yaw |
goto_target(head=...) |
-180° ~ +180° | 头部偏航角控制 |
Number |
body_yaw |
goto_target(body_yaw=...) |
-160° ~ +160° | 身体偏航角控制 |
Number |
antenna_left |
goto_target(antennas=...) |
-90° ~ +90° | 左天线角度控制 |
Number |
antenna_right |
goto_target(antennas=...) |
-90° ~ +90° | 右天线角度控制 |
Phase 4: 注视控制
| ESPHome 实体类型 | 名称 | SDK API | 范围/选项 | 说明 |
|---|---|---|---|---|
Number |
look_at_x |
look_at_world(x, y, z) |
世界坐标 | 注视点 X 坐标 |
Number |
look_at_y |
look_at_world(x, y, z) |
世界坐标 | 注视点 Y 坐标 |
Number |
look_at_z |
look_at_world(x, y, z) |
世界坐标 | 注视点 Z 坐标 |
已实现的传感器实体 (Sensors) - 只读
Phase 1 & 5: 基础状态与音频传感器
| ESPHome 实体类型 | 名称 | SDK API | 说明 |
|---|---|---|---|
Text Sensor |
daemon_state |
DaemonStatus.state |
Daemon 状态 |
Binary Sensor |
backend_ready |
backend_status.ready |
后端是否就绪 |
Text Sensor |
error_message |
DaemonStatus.error |
当前错误信息 |
Sensor |
doa_angle |
DoAInfo.angle |
声源方向角度 (°) |
Binary Sensor |
speech_detected |
DoAInfo.speech_detected |
是否检测到语音 |
Phase 6: 诊断信息
| ESPHome 实体类型 | 名称 | SDK API | 说明 |
|---|---|---|---|
Sensor |
control_loop_frequency |
control_loop_stats |
控制循环频率 (Hz) |
Text Sensor |
sdk_version |
DaemonStatus.version |
SDK 版本号 |
Text Sensor |
robot_name |
DaemonStatus.robot_name |
机器人名称 |
Binary Sensor |
wireless_version |
DaemonStatus.wireless_version |
是否为无线版本 |
Binary Sensor |
simulation_mode |
DaemonStatus.simulation_enabled |
是否在仿真模式 |
Text Sensor |
wlan_ip |
DaemonStatus.wlan_ip |
无线网络 IP |
Phase 7: IMU 传感器 (仅无线版本)
| ESPHome 实体类型 | 名称 | SDK API | 说明 |
|---|---|---|---|
Sensor |
imu_accel_x |
mini.imu["accelerometer"][0] |
X 轴加速度 (m/s²) |
Sensor |
imu_accel_y |
mini.imu["accelerometer"][1] |
Y 轴加速度 (m/s²) |
Sensor |
imu_accel_z |
mini.imu["accelerometer"][2] |
Z 轴加速度 (m/s²) |
Sensor |
imu_gyro_x |
mini.imu["gyroscope"][0] |
X 轴角速度 (rad/s) |
Sensor |
imu_gyro_y |
mini.imu["gyroscope"][1] |
Y 轴角速度 (rad/s) |
Sensor |
imu_gyro_z |
mini.imu["gyroscope"][2] |
Z 轴角速度 (rad/s) |
Sensor |
imu_temperature |
mini.imu["temperature"] |
IMU 温度 (°C) |
Phase 8-12: 扩展功能
| ESPHome 实体类型 | 名称 | 说明 |
|---|---|---|
Select |
emotion |
表情选择器 (Happy/Sad/Angry/Fear/Surprise/Disgust) |
Number |
microphone_volume |
麦克风音量 (0-100%) |
Camera |
camera |
ESPHome Camera 实体(实时预览) |
Number |
led_brightness |
LED 亮度 (0-100%) |
Select |
led_effect |
LED 效果 (off/solid/breathing/rainbow/doa) |
Number |
led_color_r |
LED 红色分量 (0-255) |
Number |
led_color_g |
LED 绿色分量 (0-255) |
Number |
led_color_b |
LED 蓝色分量 (0-255) |
Switch |
agc_enabled |
自动增益控制开关 |
Number |
agc_max_gain |
AGC 最大增益 (0-30 dB) |
Number |
noise_suppression |
噪声抑制级别 (0-100%) |
Binary Sensor |
echo_cancellation_converged |
回声消除收敛状态 |
注意: 头部位置 (x/y/z) 和角度 (roll/pitch/yaw)、身体偏航角、天线角度都是可控制的实体, 使用
Number类型实现双向控制。设置新值时调用goto_target(),读取当前值时调用get_current_head_pose()等。
实现优先级
Phase 1 - 基础状态与音量 (高优先级) ✅ 已完成
-
daemon_state- Daemon 状态传感器 -
backend_ready- 后端就绪状态 -
error_message- 错误信息 -
speaker_volume- 扬声器音量控制
-
Phase 2 - 电机控制 (高优先级) ✅ 已完成
-
motors_enabled- 电机开关 -
motor_mode- 电机模式选择 (enabled/disabled/gravity_compensation) -
wake_up/go_to_sleep- 唤醒/睡眠按钮
-
Phase 3 - 姿态控制 (中优先级) ✅ 已完成
-
head_x/y/z- 头部位置控制 -
head_roll/pitch/yaw- 头部角度控制 -
body_yaw- 身体偏航角控制 -
antenna_left/right- 天线角度控制
-
Phase 4 - 注视控制 (中优先级) ✅ 已完成
-
look_at_x/y/z- 注视点坐标控制
-
Phase 5 - 音频传感器 (低优先级) ✅ 已完成
-
doa_angle- 声源方向 -
speech_detected- 语音检测
-
Phase 6 - 诊断信息 (低优先级) ✅ 已完成
-
control_loop_frequency- 控制循环频率 -
sdk_version- SDK 版本 -
robot_name- 机器人名称 -
wireless_version- 无线版本标识 -
simulation_mode- 仿真模式标识 -
wlan_ip- 无线 IP 地址
-
Phase 7 - IMU 传感器 (可选,仅无线版本) ✅ 已完成
-
imu_accel_x/y/z- 加速度计 -
imu_gyro_x/y/z- 陀螺仪 -
imu_temperature- IMU 温度
-
Phase 8 - 表情控制 ✅ 已完成
-
emotion- 表情选择器 (Happy/Sad/Angry/Fear/Surprise/Disgust)
-
Phase 9 - 音频控制 ✅ 已完成
-
microphone_volume- 麦克风音量控制 (0-100%)
-
Phase 10 - 摄像头集成 ✅ 已完成
-
camera- ESPHome Camera 实体(实时预览)
-
Phase 11 - LED 控制 ✅ 已完成
-
led_brightness- LED 亮度 (0-100%) -
led_effect- LED 效果 (off/solid/breathing/rainbow/doa) -
led_color_r/g/b- LED RGB 颜色 (0-255)
-
Phase 12 - 音频处理参数 ✅ 已完成
-
agc_enabled- 自动增益控制开关 -
agc_max_gain- AGC 最大增益 (0-30 dB) -
noise_suppression- 噪声抑制级别 (0-100%) -
echo_cancellation_converged- 回声消除收敛状态(只读)
-
🎉 所有实体已完成!
总计:45+ 个实体
- Phase 1: 4 个实体 (基础状态与音量)
- Phase 2: 4 个实体 (电机控制)
- Phase 3: 9 个实体 (姿态控制)
- Phase 4: 3 个实体 (注视控制)
- Phase 5: 2 个实体 (音频传感器)
- Phase 6: 6 个实体 (诊断信息)
- Phase 7: 7 个实体 (IMU 传感器)
- Phase 8: 1 个实体 (表情控制)
- Phase 9: 1 个实体 (麦克风音量)
- Phase 10: 1 个实体 (摄像头)
- Phase 11: 5 个实体 (LED 控制)
- Phase 12: 4 个实体 (音频处理参数)
SDK 数据结构参考
# 电机控制模式
class MotorControlMode(str, Enum):
Enabled = "enabled" # 扭矩开启,位置控制
Disabled = "disabled" # 扭矩关闭
GravityCompensation = "gravity_compensation" # 重力补偿模式
# Daemon 状态
class DaemonState(Enum):
NOT_INITIALIZED = "not_initialized"
STARTING = "starting"
RUNNING = "running"
STOPPING = "stopping"
STOPPED = "stopped"
ERROR = "error"
# 完整状态
class FullState:
control_mode: MotorControlMode
head_pose: XYZRPYPose # x, y, z (m), roll, pitch, yaw (rad)
head_joints: list[float] # 7 个关节角度
body_yaw: float
antennas_position: list[float] # [right, left]
doa: DoAInfo # angle (rad), speech_detected (bool)
# IMU 数据 (仅无线版本)
imu_data = {
"accelerometer": [x, y, z], # m/s²
"gyroscope": [x, y, z], # rad/s
"quaternion": [w, x, y, z], # 姿态四元数
"temperature": float # °C
}
# 安全限制
HEAD_PITCH_ROLL_LIMIT = [-40°, +40°]
HEAD_YAW_LIMIT = [-180°, +180°]
BODY_YAW_LIMIT = [-160°, +160°]
YAW_DELTA_MAX = 65° # 头部与身体偏航角最大差值
ESPHome 协议实现说明
ESPHome 协议通过 protobuf 消息与 Home Assistant 通信。需要实现以下消息类型:
from aioesphomeapi.api_pb2 import (
# Number 实体 (音量/角度控制)
ListEntitiesNumberResponse,
NumberStateResponse,
NumberCommandRequest,
# Select 实体 (电机模式)
ListEntitiesSelectResponse,
SelectStateResponse,
SelectCommandRequest,
# Button 实体 (唤醒/睡眠)
ListEntitiesButtonResponse,
ButtonCommandRequest,
# Switch 实体 (电机开关)
ListEntitiesSwitchResponse,
SwitchStateResponse,
SwitchCommandRequest,
# Sensor 实体 (数值传感器)
ListEntitiesSensorResponse,
SensorStateResponse,
# Binary Sensor 实体 (布尔传感器)
ListEntitiesBinarySensorResponse,
BinarySensorStateResponse,
# Text Sensor 实体 (文本传感器)
ListEntitiesTextSensorResponse,
TextSensorStateResponse,
)