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.
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.