This is the major change. All the _is_speaking, _run_finished, etc. flags are gone, replaced by a single self._state variable and a _set_state method. The logic is now much cleaner.
Goal: satellite.py currently uses a set of boolean flags (_is_streaming_audio, _tts_played, _run_finished, _is_speaking) to figure out what it's doing. This is hard to read and easy to break. Replaced this with a clean, formal state machine (using an Enum). This makes the logic explicit and much easier to manage.
added the SatelliteState Enum at the top and removed the media_player_entity from ServerState as it's better managed by the satellite itself
Goal: We'll apply the exact same pattern to the MqttController. The paho-mqtt library runs its _on_message callback in a separate thread. We'll stop it from modifying state directly and instead schedule a new function to run on the main asyncio loop.
Here is the complete file. The key changes are:
The _on_message function is now tiny. It just gets the topic/payload and uses self.state.loop.call_soon_threadsafe to schedule a new function.
A new function _handle_message_on_loop contains all the logic that used to be in _on_message. This new function will run safely on the main asyncio loop, eliminating the race condition.
Fix MpvMediaPlayer Race Condition
Goal: The mpv library runs in its own thread. When a track finishes, its callback (_on_idle_active) runs in that thread and tries to modify the satellite's state, creating a race condition. We will fix this by "marshaling" that callback onto the main asyncio loop.
added loop=loop to the MpvMediaPlayer constructors
Selects the best available audio output backend for mpv.
The selection follows a clear priority:
1. A fully qualified device string (e.g., 'alsa/plughw:...') is used directly.
2. A simple device name tries PipeWire, then PulseAudio, then ALSA.
3. No device specified (or 'default') tries the default for PipeWire, PulseAudio, and ALSA in order.
Includes various compatibility fixes, reversions, and clean-up from the previous development cycle to create a stable base for the MQTT implementation. This ensures the application starts reliably with the pinned aioesphomeapi version.
Includes various compatibility fixes, reversions, and clean-up from the previous development cycle to create a stable base for the MQTT implementation. This ensures the application starts reliably with the pinned aioesphomeapi version.
Adds a handler for the VOICE_ASSISTANT_ERROR event, publishing a voice_error event to the EventBus so the LedController can provide visual feedback.
Fixes a race condition where the "Responding" LED effect was being cancelled prematurely, ensuring it now plays for the full duration of the TTS audio.
Adds command-line arguments (--mqtt-host, --mqtt-port, etc., and --num-leds) to configure the MQTT connection and initial LED setup.
Instantiates and starts the MqttController on application startup if an MQTT host is provided.
Includes the MicMuteHandler class, which listens for mute commands, updates the global mic_muted state, and clears the audio queue on unmute to prevent processing stale audio.
Updates the process_audio loop to respect the mic_muted flag.
Refactored for Full Configurability: The controller has been re-architected to remove all hard-coded effects and colors.
Central Configuration: It now uses a central configs dictionary to store the effect, color, and brightness settings for each voice assistant state (idle, listening, etc.).
Dynamic State Management: Subscribes to new set_<state>_effect and set_<state>_color events from the event bus to update its configuration in real-time.
Applies Custom Effects: The main voice event handlers (voice_stt_start, voice_stt_end, etc.) have been simplified to look up the user's chosen configuration and apply it.
New Animations: Implements a full suite of animations (Slow/Medium/Fast Pulse, Slow/Medium/Fast Blink, Spin) to be used by the effect selectors.
Introduces the MqttController, a new class that manages all communication with the MQTT broker.
On startup, it connects to the broker and publishes discovery configurations for all entities, automatically creating them in Home Assistant.
Subscribes to command topics to receive instructions from the HA interface.
Subscribes to state topics to read retained messages on startup, ensuring all user settings are persistent.
Acts as a bridge, translating incoming MQTT messages into internal EventBus events for other components to act upon.
Subscribes to the new mic_muted and mic_unmuted events.
Provides visual feedback by setting the LEDs to a solid dim red when muted and calling the idle state handler (turning the LEDs off) when unmuted.
Implements the core mute functionality.
Adds a new MicMuteHandler class that subscribes to the set_mic_mute event. This handler updates the state.mic_muted flag and publishes secondary mic_muted or mic_unmuted events for LED feedback.
Modifies the process_audio function to check the state.mic_muted flag in its main loop, pausing audio processing when the flag is True.
Instantiates the MicMuteSwitchEntity on startup to register it with Home Assistant.
Updates the central message handler to correctly route SwitchCommandRequest messages from Home Assistant to the appropriate entity.
Defines a new MicMuteSwitchEntity class that creates the switch entity in Home Assistant.
When the switch is toggled, it publishes a set_mic_mute event to the internal event bus with the new state (True for muted, False for unmuted).
Updates the MediaPlayerEntity to handle the saving and loading logic.
The entity's internal volume state is now initialized from the saved preferences.volume_level.
When a volume change command is received from Home Assistant, the handle_message method now updates the Preferences object and calls save_preferences() to persist the new volume level to disk.
Modifies the startup sequence to read the volume_level from the loaded preferences.
This saved value is then passed as the initial_volume when creating the music_player and tts_player instances.
Updates the MpvMediaPlayer constructor (__init__) to accept an initial_volume parameter.
This allows the volume to be set immediately when the mpv process is created, applying the saved setting at startup.
Adds a new volume_level: float field to the Preferences dataclass. This creates a dedicated entry in the preferences.json file to store the user's desired volume (from 0.0 to 1.0).