Integrate Home Assistant voice assistant functionality into Reachy Mini Wi-Fi robot, communicating with Home Assistant via ESPHome protocol.
Current package:
reachy_mini_home_assistant
Current version:
1.0.8
- linux-voice-assistant - Linux-based Home Assistant voice assistant app for reference
- Reachy Mini SDK - Reachy Mini SDK local directory for reference
- reachy_mini_conversation_app - Reachy Mini conversation app for reference
- reachy-mini-desktop-app - Reachy Mini desktop app for reference
- sendspin - Sendspin client for reference
- aiosendspin - Sendspin protocol client library reference
- dynamic_gestures - Dynamic gesture reference
- SimpleDances - Local reference snapshot
- Zero Configuration - Users only need to install the app, no manual configuration required
- Native Hardware - Use robot's built-in microphone and speaker
- Home Assistant Centralized Management - STT, TTS, and intent configuration stay on the Home Assistant side
- Motion Feedback - Provide head movement and antenna animation feedback during voice interaction
- Project Constraints - Strictly follow Reachy Mini SDK architecture design and current SDK constraints
- Code Quality - Follow Python development standards with consistent code style, clear structure, and maintainability
- Feature Priority - Voice conversation with Home Assistant is highest priority; other features are auxiliary and must not affect voice conversation functionality or response speed
- No LED Functions - LEDs are hidden inside the robot; all LED control is ignored
- Preserve Functionality - Any code modifications should optimize while preserving completed features; do not remove features to solve problems
- No App-Managed Sleep/Wake - The app no longer manages robot sleep/wake transitions; current SDK behavior is treated as source of truth
The current system is built around one main runtime service with motion, vision, audio, and Home Assistant entity subsystems.
ReachyMiniHaVoice (main.py)
-> VoiceAssistantService (voice_assistant.py)
-> VoiceSatelliteProtocol server (protocol/satellite.py)
-> Reachy Mini motion runtime (motion/)
-> Camera and vision runtime (vision/)
-> Local speech player + optional Sendspin player (audio/)
-> Home Assistant entities and state publishing (entities/)
Current startup path:
ReachyMiniHaVoiceinmain.pyis the Reachy Mini app entry pointrun()creates oneVoiceAssistantServiceVoiceAssistantService.start()initializes runtime directories, loads wake word assets, loads preferences, builds server state, starts SDK media, starts the motion runtime, starts the audio processing thread, starts the ESPHome server, and registers mDNS discovery- Sendspin discovery is started only if enabled in stored preferences
- The camera runtime is not started unconditionally at boot; it is reconciled later based on Home Assistant connection state, camera enable state, and idle behavior state
This means the real root of the current runtime is VoiceAssistantService, not the protocol class and not the camera server.
Current always-important runtime objects:
| Object | Defined in | Current role |
|---|---|---|
ReachyMiniHaVoice |
main.py |
App wrapper used by Reachy Mini SDK |
VoiceAssistantService |
voice_assistant.py |
Main runtime orchestrator |
ServerState |
models.py |
Shared mutable runtime state passed into protocol and entity layers |
VoiceSatelliteProtocol |
protocol/satellite.py |
One ESPHome voice assistant protocol connection handler |
MovementManager |
motion/movement_manager.py |
Unified movement and pose composition loop |
MJPEGCameraServer |
vision/camera_server.py |
Camera HTTP server plus face and gesture runtime |
LocalAudioPlayer |
audio/local_audio_player.py |
Local speech, wakeup, and timer playback |
AudioPlayer |
audio/audio_player.py |
Music-capable playback facade with optional Sendspin support |
The current audio input architecture is centered in VoiceAssistantService.
Actual runtime path:
VoiceAssistantService.start()validates SDK media availability- The service starts SDK recording and playback through
reachy_mini.media _process_audioinvoice_assistant.pyruns on a dedicated thread- That thread reads microphone samples from the SDK media backend
- The same thread performs local wake word and stop word inference
- When a voice session is active, the protocol object is used to forward audio chunks to Home Assistant
Important current constraint:
- Audio capture, local wake detection, and protocol audio forwarding are tied together through
VoiceAssistantService; they are not separate daemons or separately owned services
The current Home Assistant voice path is centered in VoiceSatelliteProtocol and helper modules under protocol/.
Actual structure:
VoiceAssistantServicecreates an asyncio server usingVoiceSatelliteProtocol- Each protocol instance receives the shared
ServerState, optional camera server, and a back-reference toVoiceAssistantService protocol/message_dispatch.pyroutes incoming protobuf messagesprotocol/session_flow.pymanages wakeup sequencing, wakeup sound completion, pending voice request flow, and delayed idle return helpersprotocol/voice_pipeline.pymanages TTS playback, timer playback, ducking, unducking, and stop behaviorprotocol/motion_bridge.pymaps voice assistant phases to robot motion callbacksprotocol/entity_bridge.pyconnects protocol state with entity setup and camera callbacks
Current protocol state held inside VoiceSatelliteProtocol includes:
- whether audio streaming is active
- pending TTS URL
- timer playback state
- pending wakeup-triggered voice request
- continuous conversation state
- conversation ID and timeout tracking
- delayed return-to-idle timer
- per-connection Home Assistant entity state cache
The current motion system is not just a set of direct SDK calls. It is a composed control runtime centered on MovementManager.
Actual structure:
VoiceAssistantServicecreatesReachyMiniMotion, which exposesmovement_managermovement_manager.pyowns the long-running control loop thread- External requests are pushed into a command queue instead of directly mutating robot state from many threads
- The loop composes final head pose, antenna pose, and body yaw before sending commands
- Command polling and state transition handling are split into
command_runtime.py - Control emission helpers are split into
control_runtime.py - Idle behavior logic is split into
idle_runtime.py - Smoothing and pose composition are split into
smoothing.pyandpose_composer.py
Current motion inputs that feed the final composed pose:
- explicit pose commands from Home Assistant entities
- robot state transitions such as listening, thinking, speaking, and idle
- emotion or action moves
- speech sway (SDK daemon-side head wobbler via
enable_wobbling()) - face tracking offsets (SDK daemon-side YuNet tracker)
- idle behavior and idle random actions
- DOA-triggered turn-to-sound behavior
Current DOA integration:
MovementManagerowns aDOATracker- wakeup turn-to-sound behavior is invoked from the protocol layer
- DOA is part of current runtime behavior, but it is not a separate architecture layer
The current camera system is conditional and service-managed, not always-on.
Actual lifecycle:
VoiceAssistantServicedecides whether the camera runtime should exist_reconcile_camera_runtime()starts or stopsMJPEGCameraServerdepending on:- app camera enable flag
- Home Assistant connection state
- stored idle behavior preference
MJPEGCameraServerowns the HTTP camera surface and capture thread- Runtime helper modules split camera responsibilities:
camera_runtime.pyfor lifecycle and model load/unload helperscamera_processing.pyfor frame capture, AI processing, and stream helperscamera_http.pyfor/,/stream, and/snapshothandlers
- Face tracking and gesture detection are optional capabilities inside the same camera runtime
Current important behavior:
- camera runtime can be fully stopped when idle behavior is off
- the stream is viewer-aware to reduce MJPEG work when no client is connected
/snapshotcan encode on demand- face and gesture models can be independently requested through runtime preferences
The current playback design intentionally separates local speech playback from Sendspin-capable playback.
Actual split:
LocalAudioPlayeris stored inServerState.tts_playerAudioPlayeris stored inServerState.music_player- TTS, wakeup, and timer sounds go through the local player path
- Sendspin discovery, connection, buffering, remote commands, and synchronized playback live in the music player path
- Speech-driven head motion is delegated to the SDK daemon-side head wobbler (
ReachyMini.enable_wobbling()); the app does not compute sway offsets locally
Current audio module split:
audio_player_playback.pyhandles generic playback lifecycleaudio_player_local.pyhandles local file and in-memory fallback playbackaudio_player_stream_pcm.pyhandles streamed PCM playbackaudio_player_stream_decoded.pyhandles decoded streamed audio playbackaudio_player_sendspin.pyhandles Sendspin integrationaudio_player_shared.pyholds shared constants and helpers
The current entity architecture is built from setup helpers rather than one giant registry file.
Actual structure:
VoiceSatelliteProtocolcreates the entity registry throughentity_bridge.pyinitialize_entities()wires entities only once per shared server stateruntime_entity_setup.pyregisters runtime control entities such as:speaker_volumemutecamera_disabledidle_behavior_enabledsendspin_enabledface_tracking_enabledgesture_detection_enabledemotioncontinuous_conversationthinking_sound_enabledwake_word_sensitivitystop_word_sensitivity
sensor_entity_setup.pyregisters state, observation, and diagnostic entitiesentity_registry.pykeeps references to live entity objects and pushes updates back to Home Assistant
Current important behavior:
- many switches are backed by persisted preferences
- vision-related switches call back into camera runtime state application
mutedirectly suspends or resumes voice services throughVoiceAssistantServicesendspin_enableddirectly toggles Sendspin discovery and connection throughVoiceAssistantService
The current system has two suspension scopes, both owned by VoiceAssistantService.
Voice-only suspension:
_suspend_voice_services()and_resume_voice_services()- used for mute-like behavior
- stops or resumes media and protocol voice path while leaving camera and motion available
Non-ESPHome service suspension:
_suspend_non_esphome_services()and_resume_non_esphome_services()- can suspend camera, motion, audio players, satellite runtime, and media system together while keeping the outer ESPHome presence alive
This is the real current architecture for runtime pause behavior. It is not based on the removed app-managed sleep/wake design.
The current conversation flow is driven by wake detection plus protocol state.
idle
-> local wake word detected
-> wakeup sound and optional turn-to-sound
-> listening
-> thinking
-> speaking
-> delayed return or direct return to idle
Current interruption paths:
- stop word during interruptible context
- timer ring interruption flow
- Home Assistant disconnection
- mute-driven voice suspension
The current system boundary is:
- Reachy Mini SDK owns robot media and hardware access
- this app owns wake logic, playback logic, motion feedback, optional camera AI, and Home Assistant entity behavior
- Home Assistant owns speech recognition, intent handling, and response generation
That is the current project architecture reflected by the codebase.
- Install the app as a Reachy Mini app
- Start the app on the robot
- The app initializes SDK media, loads wake word assets, and starts the ESPHome server on port
6053 - Home Assistant discovers the robot automatically through mDNS, or it can be added manually as an ESPHome device
- The user says a wake phrase such as
Okay Nabu - The app streams audio to Home Assistant and waits for conversation events or TTS audio in return
- Reachy Mini plays local speech audio, applies motion feedback, and returns to idle when the interaction finishes
Manual runtime controls are exposed through Home Assistant entities, including mute, idle behavior, continuous conversation, camera control, face tracking, gesture detection, and Sendspin enablement
sendspin_enabled: off by defaultface_tracking_enabled: off by defaultgesture_detection_enabled: off by defaultcontinuous_conversation: user-controlledidle_behavior_enabled: user-controlledlisten_during_wake_sound: off by defaultthinking_sound_enabled: off by defaultwake_word_sensitivity/stop_word_sensitivity: default0.7
Behavior notes:
- When idle behavior is off, the camera server is stopped to reduce resource usage
- When face or gesture features are disabled, their models are unloaded
- Camera snapshots can be generated on demand when stream cache is empty
- TTS playback stays on the local player path even when Sendspin support is enabled
- Sendspin music playback is paused during voice assistant activity
- The app no longer owns sleep/wake state transitions; current SDK behavior is treated as authoritative
- Audio block size is
512samples in the current runtime
Conversation-related motion behavior:
- Wakeup can turn the head toward the current sound source using DOA information
- Listening, thinking, speaking, and idle phases each map to specific motion states
- Head, body yaw, antenna motion, breathing, and speech sway are combined through the motion stack
- Speech sway and face tracking aim are composed by the SDK daemon (head wobbler and YuNet tracker); the app pushes weights via
enable_wobbling()andstart_head_tracking() - Built-in emotion moves and Home Assistant-triggered behaviors run through a shared behavior layer
Idle behavior notes:
- Idle behavior is user-controlled and persisted through preferences
- When idle behavior is off, the robot stays in a parked low-resource state
- When idle behavior is on, the motion runtime may use breathing, idle rest poses, and other configured behavior resources
Camera and AI behavior:
- The MJPEG stream is viewer-aware to avoid unnecessary continuous encoding work
/snapshotcan encode on demand when no cached frame is available- Face tracking and gesture detection can continue independently of active stream viewers when their runtimes are enabled
- Face tracking is delegated to the SDK daemon-side YuNet tracker (no in-process face detector); the app only pushes a blend weight via
start_head_tracking(weight)and samplesget_tracked_face()to publish theface_detectedstate - The YuNet face tracker model (
face_detection_yunet_2026may.onnx) is bundled undermodels/and seeded into the Hugging Face cache at startup (vision/model_assets.py) so the SDK loads it without a runtime download - Gesture detection uses ONNX models and a gesture smoother for stable result publishing
Current feature toggles:
camera_disabledface_tracking_enabledgesture_detection_enabled
Top-level package layout:
reachy_mini_home_assistant/
__main__.py
main.py
models.py
reachy_controller.py
voice_assistant.py
animations/
audio/
core/
entities/
motion/
protocol/
sounds/
static/
vision/
wakewords/
Key modules:
main.py- Reachy Mini app entry pointvoice_assistant.py- runtime orchestration, media startup, audio thread managementreachy_controller.py- SDK-facing control wrappermodels.py- shared state and preference models
Core infrastructure:
core/config.py- centralized configurationcore/service_base.py- suspend/resume-aware service helperscore/system_diagnostics.py- runtime diagnosticscore/exceptions.py- custom exception definitionscore/util.py- common helpers
Protocol layer:
protocol/satellite.py- ESPHome protocol facadeprotocol/api_server.py- protocol HTTP surfaceprotocol/entity_bridge.py- entity/protocol glueprotocol/message_dispatch.py- ESPHome message dispatchprotocol/motion_bridge.py- voice-to-motion transition helpersprotocol/session_flow.py- conversation lifecycle helpersprotocol/voice_pipeline.py- voice event, TTS, stop, ducking flowprotocol/wakeword_assets.py- wake word asset loading helpersprotocol/zeroconf.py- mDNS discovery
Motion layer:
motion/movement_manager.py- unified motion control loopmotion/command_runtime.py- command queue and state transitionsmotion/control_runtime.py- control loop helpersmotion/idle_runtime.py- idle behavior handlingmotion/pose_composer.py- multi-source pose compositionmotion/smoothing.py- pose smoothingmotion/animation_player.py- animation playbackmotion/emotion_moves.py- built-in emotion actionsmotion/antenna.py- antenna behavior controlmotion/reachy_motion.py- motion API wrappermotion/state_machine.py- motion state definitions
Vision layer:
vision/camera_server.py- MJPEG camera server facadevision/camera_runtime.py- camera lifecycle helpersvision/camera_processing.py- frame capture and processing helpersvision/camera_http.py- stream and snapshot handlersvision/gesture_detector.py- gesture detection runtimevision/gesture_smoother.py- gesture result stabilizationvision/frame_processor.py- frame pacingvision/model_assets.py- bundled model asset seeding (SDK YuNet face tracker)
Audio layer:
audio/audio_player.py- music/sendspin playback facadeaudio/local_audio_player.py- local speech playback facadeaudio/audio_player_playback.py- playback lifecycle helpersaudio/audio_player_local.py- local file and fallback playback helpersaudio/audio_player_stream_pcm.py- streamed PCM playbackaudio/audio_player_stream_decoded.py- decoded stream playbackaudio/audio_player_sendspin.py- Sendspin integrationaudio/audio_player_shared.py- shared constants and helpersaudio/doa_tracker.py- direction-of-arrival tracking
Entity layer:
entities/entity.py- base ESPHome entity typesentities/entity_factory.py- entity constructionentities/entity_registry.py- runtime registryentities/entity_extensions.py- extended entity implementationsentities/entity_keys.py- entity key constantsentities/runtime_entity_setup.py- runtime and control entitiesentities/sensor_entity_setup.py- sensor and diagnostic entitiesentities/event_emotion_mapper.py- Home Assistant event to emotion mappingentities/emotion_detector.py- currently disabled text emotion path
Top-level repository structure:
reachy_mini_ha_voice/
CHANGELOG.md
Project_Summary.md
README.md
changelog.json
pyproject.toml
docs/
home_assistant_blueprints/
reachy_mini_home_assistant/
reference/
scripts/
tests/
Application package structure:
reachy_mini_home_assistant/
__init__.py
__main__.py
main.py
models.py
reachy_controller.py
voice_assistant.py
animations/
animation_config.py
conversation_animations.json
audio/
audio_player.py
local_audio_player.py
audio_player_playback.py
audio_player_local.py
audio_player_stream_pcm.py
audio_player_stream_decoded.py
audio_player_sendspin.py
audio_player_shared.py
doa_tracker.py
core/
config.py
exceptions.py
service_base.py
system_diagnostics.py
util.py
entities/
entity.py
entity_extensions.py
entity_factory.py
entity_keys.py
entity_registry.py
event_emotion_mapper.py
runtime_entity_setup.py
sensor_entity_setup.py
emotion_detector.py
motion/
animation_player.py
antenna.py
command_runtime.py
control_runtime.py
emotion_moves.py
idle_runtime.py
movement_manager.py
pose_composer.py
reachy_motion.py
smoothing.py
state_machine.py
protocol/
api_server.py
entity_bridge.py
message_dispatch.py
motion_bridge.py
satellite.py
session_flow.py
voice_pipeline.py
wakeword_assets.py
zeroconf.py
sounds/
static/
vision/
camera_http.py
camera_processing.py
camera_runtime.py
camera_server.py
frame_processor.py
gesture_detector.py
gesture_smoother.py
wakewords/
Core voice assistant:
- ESPHome voice assistant server
- mDNS auto-discovery
- Local wake word detection
- Stop word detection
- Audio streaming to Home Assistant
- Local TTS playback
- Continuous conversation toggle
Reachy Mini integration:
- SDK microphone input
- SDK speaker output
- Head, body yaw, and antenna motion control
- Speech-phase motion feedback
- Built-in emotion and animation support
Vision and tracking:
- Home Assistant camera entity
- MJPEG stream server
- On-demand snapshots
- Face tracking
- Gesture detection with runtime enable/disable
Audio enhancements:
- Local speech playback path
- Optional Sendspin synchronized audio playback
- Ducking during conversation
- Speech sway delegated to the SDK daemon-side head wobbler
Diagnostics and control:
- Runtime suspend/resume state
- System diagnostics entities
- Camera/face/gesture/sendspin switches
- Idle behavior switch
The exact entity count evolves over time, but the current runtime exposes entities in these groups:
- Voice assistant and media entities
- Runtime switches and preferences
- Head, body, and antenna pose control entities
- Gaze target entities
- DOA and speech-related sensors
- Face tracking and gesture sensors
- Camera entity
- System diagnostic sensors
- Emotion and behavior control entities
Representative control entities:
speaker_volumemuteidle_behavior_enabledcontinuous_conversationsendspin_enabledcamera_disabledface_tracking_enabledgesture_detection_enabledthinking_sound_enabledwake_word_sensitivitystop_word_sensitivityhead_x,head_y,head_zhead_roll,head_pitch,head_yawbody_yawantenna_left,antenna_rightlook_at_x,look_at_y,look_at_zemotion
Representative sensor entities:
daemon_statebackend_readyerror_messagedoa_anglespeech_detectedservices_suspendedgesture_detectedgesture_confidenceface_detectedsdk_versionrobot_namewlan_ip- system diagnostic entities such as CPU, memory, disk, uptime, and process metrics
Implemented core entities:
| Entity Type | Name | Purpose |
|---|---|---|
| Media Player | media_player |
Audio playback control |
| Voice Assistant | voice_assistant |
Voice assistant pipeline integration |
Implemented control entities:
| Entity Type | Name | Description |
|---|---|---|
| Number | speaker_volume |
Speaker volume control |
| Switch | mute |
Suspend or resume the voice pipeline |
| Switch | idle_behavior_enabled |
Unified idle motion and idle behavior toggle |
| Switch | camera_disabled |
Disable or enable camera runtime |
| Switch | sendspin_enabled |
Enable or disable Sendspin playback integration |
| Switch | face_tracking_enabled |
Enable or disable face tracking |
| Switch | gesture_detection_enabled |
Enable or disable gesture detection |
| Switch | continuous_conversation |
Multi-turn conversation mode |
| Switch | thinking_sound_enabled |
Enable or disable the thinking sound |
| Number | wake_word_sensitivity |
Wake word sensitivity |
| Number | stop_word_sensitivity |
Stop word sensitivity |
| Select | emotion |
Manual emotion trigger |
| Number | head_x, head_y, head_z |
Head position control |
| Number | head_roll, head_pitch, head_yaw |
Head angle control |
| Number | body_yaw |
Body yaw control |
| Number | antenna_left, antenna_right |
Antenna angle control |
| Number | look_at_x, look_at_y, look_at_z |
Gaze target control |
Implemented sensor entities:
| Entity Type | Name | Description |
|---|---|---|
| Text Sensor | daemon_state |
Daemon state |
| Binary Sensor | backend_ready |
Backend ready status |
| Text Sensor | error_message |
Current error message |
| Sensor | doa_angle |
Sound source direction angle |
| Binary Sensor | speech_detected |
Speech detection status |
| Binary Sensor | services_suspended |
Runtime suspension state |
| Text Sensor | gesture_detected |
Current detected gesture |
| Sensor | gesture_confidence |
Current gesture confidence |
| Binary Sensor | face_detected |
Face visibility state |
| Text Sensor | sdk_version |
SDK version |
| Text Sensor | robot_name |
Robot name |
| Text Sensor | wlan_ip |
Wireless IP address |
| Text Sensor | wireless_version |
Wireless variant flag |
| Binary Sensor | simulation_mode |
Simulation mode flag |
| Sensor | control_loop_frequency |
Motion control loop frequency |
| Sensor | imu_accel_x/y/z, imu_gyro_x/y/z, imu_temperature |
IMU accelerometer, gyroscope, and temperature |
| Camera | camera |
Live preview and snapshots |
System diagnostic entities include CPU, temperature, memory, disk, uptime, and process metrics.
Notes:
- Head position, head angles, body yaw, and antenna angles are all controllable runtime entities
- Gaze target entities are exposed as world-coordinate controls
- LED entities are intentionally not part of the current supported runtime surface
Implemented core capabilities:
- ESPHome voice assistant integration
- Local wake word detection
- Stop word interruption
- TTS playback
- Continuous conversation toggle
- Reachy Mini motion feedback
- Camera streaming and snapshots
- Face tracking
- Gesture detection
- Sendspin integration
- Runtime diagnostics and Home Assistant controls
Important current constraints:
- STT, TTS voice generation, and intent handling stay on the Home Assistant side
- Optional features must not block or slow down core conversation behavior
- App-managed sleep/wake lifecycle has been removed because it no longer matches current SDK expectations
- LEDs are intentionally not part of the supported feature surface
Phase-by-phase status carried forward in current terminology:
| Phase | Area | Status | Notes |
|---|---|---|---|
| 1 | Basic status and volume | Completed | Core runtime switches and volume are exposed |
| 2 | Runtime state | Completed | services_suspended remains; app-managed sleep entities were removed |
| 3 | Pose control | Completed | Head, body yaw, and antenna controls are exposed |
| 4 | Gaze control | Completed | look_at_x/y/z supported |
| 5 | DOA integration | Completed | Used for wakeup turn-to-sound and exposed through sensors |
| 6 | Diagnostic information | Completed | SDK and runtime diagnostics exposed |
| 7 | IMU sensors | Completed | Exposed where supported by the robot/runtime |
| 8 | Emotion control | Completed | Manual emotion trigger via select entity |
| 10 | Camera integration | Completed | Camera entity plus MJPEG runtime |
| 11 | LED control | Disabled by design | Not part of supported user-facing scope |
| 13 | Sendspin support | Completed | Optional synchronized playback path |
| 14 | Emotion and motion feedback | Completed | Manual emotion plus conversation-phase feedback |
| 15 | Face tracking | Completed | DOA wakeup turn plus face tracking runtime |
| 16 | Animation system | Completed | JSON-driven animation behavior integrated |
| 17 | Antenna sync during speech | Completed | Included in current animation/motion runtime |
| 18 | Visual gaze interaction | Completed | Current single-face-oriented runtime behavior |
| 19 | Gravity compensation teaching | Not a current runtime target | Historical exploration only |
| 20 | Environment awareness | Partial | IMU exposure exists; higher-level reactions remain limited |
| 21 | Continuous conversation | Completed | Runtime switch exposed |
| 22 | Gesture detection | Completed | Runtime detection and state publishing implemented |
| 23 | Face detection sensor | Completed | Binary sensor exposed |
| 24 | System diagnostics | Completed | Diagnostic sensor set exposed |
Current implemented behavior:
- Manual emotion playback through the
emotionselect entity - Wake, listening, thinking, speaking, idle, and timer-complete state transitions drive local motion changes
- Conversation-phase reactions are non-blocking and route through the motion system rather than taking over the whole app runtime
- Home Assistant-triggered event behavior is mapped through the built-in behavior layer
Deliberately not active in the current runtime:
- Automatic emotion inference from assistant text output
- User-configured choreography layers that would break zero-config expectations
Current model (SDK-owned):
- Face tracking is fully delegated to the Reachy Mini SDK daemon-side YuNet tracker
- DOA is used once at wakeup to orient toward the speaker
- The app pushes a per-voice-phase blend weight via
start_head_tracking(weight)(0 pauses the tracker, >0 blends the tracked aim into the SDK IK output) - Body yaw follows head orientation for more natural tracking behavior
- A poller thread samples
get_tracked_face()to publish theface_detectedbinary state to Home Assistant
Implemented aspects:
- Daemon-side face tracking runtime (SDK
start_head_tracking/get_tracked_face) - Per-phase tracking weight blending
- Face visibility state publishing to Home Assistant
- The poller thread is restarted on Home Assistant reconnect so
face_detectedkeeps updating
Current animation model:
- Animation definitions are driven from
conversation_animations.json - Motion state transitions use the animation system plus direct motion overlays
- Speech sway is delegated to the SDK daemon-side head wobbler via
ReachyMini.enable_wobbling()(the SDK analyses the speech signal and composes sway offsets into its IK output); the app no longer computes sway offsets locally - Idle behavior can combine rest pose, breathing, antenna behavior, and other built-in patterns
Current gesture behavior:
- ONNX-based hand and gesture models are bundled in the project
- Detection can be enabled or disabled at runtime
- Results are published to Home Assistant entities
- Runtime behavior is state publishing only; gesture-driven robot actions are intentionally not the main path
Current Sendspin behavior:
- Optional integration for synchronized audio playback
- Separate from local TTS playback path
- Automatically paused or ducked around voice assistant activity as needed
- Uses the current
aiosendspinintegration line and local buffering/backpressure handling
Important historical context that still matters:
- The project previously carried more legacy compatibility code and app-managed sleep/wake behavior; current runtime intentionally removed those paths
- Camera runtime evolved from a single large module into split runtime, processing, and HTTP helper modules
- Audio runtime evolved from one mixed player path into clearer local speech and Sendspin-capable music paths
- The document previously contained large box-drawing diagrams and heavily versioned planning sections; those were the main source of encoding corruption and were replaced with plain Markdown structure
Current test files in tests/:
test_animation_config.pytest_camera_gesture_processing.pytest_command_runtime.pytest_emotion_detector.py
Current coverage is focused on animation config, command runtime behavior, gesture/camera processing, and emotion-related logic.
Current important runtime dependencies (from pyproject.toml):
reachy-mini>=1.9.0
soundfile>=0.13.0
numpy>=2.2.5,<3.0.0
opencv-python>=4.12.0.88
pymicro-wakeword>=2.0.0,<3.0.0
pyopen-wakeword>=1.0.0,<2.0.0
aioesphomeapi>=43.10.1
zeroconf>=0.131,<1
websockets>=12,<16
aiohttp>=3.9.0
scipy>=1.15.3,<2.0.0
aiosendspin>=6.0,<7.0
onnxruntime>=1.18.0
pillow>=10.0,<12.0
pydantic>=2.0,<=2.12.5
requests>=2.33.0Recent project direction reflected by the current codebase:
- The app is aligned with current Reachy Mini SDK media behavior (baseline
reachy-mini>=1.9.0, tested against the v1.10.x release line) - Legacy compatibility paths have been removed; SDK overlap is delegated to the SDK (daemon-side YuNet face tracker and head wobbler)
- TTS and Sendspin playback paths are separated for clearer runtime behavior
- Camera streaming is viewer-aware to reduce unnecessary CPU usage
- Idle-off mode is treated as a low-resource parked runtime rather than a legacy sleep mode
Recent version milestones relevant to the current state:
1.0.5removed app-managed sleep/wake integration and aligned with newer SDK behavior1.0.6aligned the dependency baseline with newer SDK releases and improved camera snapshot/runtime handling1.0.7split local TTS playback from Sendspin-capable music playback and tightened shared audio runtime behavior1.0.8resolved the STT silence bug (_is_streaming_audioon RUN_START,listen_during_wake_sound), migrated to theaiosendspin6.x API, replaced private SDK client access with the daemon REST API, and moved face tracking / speech sway fully onto SDK daemon-side APIs (YuNet tracker and head wobbler)
This document should describe the current implementation, not an idealized roadmap. If a feature is disabled, optional, or runtime-gated, document it that way instead of presenting it as always active.
When updating this document:
- Prefer current code structure over historical descriptions
- Remove outdated version snapshots rather than stacking them
- Avoid box-drawing diagrams that are likely to suffer encoding corruption
- Keep dependency versions aligned with
pyproject.toml - Keep feature statements aligned with current runtime defaults, not planned behavior