feat: refactor MQTT from global config to multi-instance entity model
Lint & Test / test (push) Successful in 1m32s

MQTT broker connections are now managed as entities (like HA sources)
instead of a single global config. Each MQTTSource gets its own
runtime with auto-reconnect, ref-counted via MQTTManager.

Backend:
- MQTTSource dataclass + MQTTSourceStore (SQLite)
- MQTTRuntime (per-broker connection, refactored from MQTTService)
- MQTTManager (ref-counted pool, same pattern as HAManager)
- CRUD API at /api/v1/mqtt/sources + test + status endpoints
- MQTTRule gains mqtt_source_id field for source selection
- Automation engine acquires/releases MQTT runtimes automatically
- Legacy MQTTService kept for backward compat during transition

Frontend:
- MQTT source cards in Streams > Integrations tab
- Create/edit modal with test button
- Dashboard integration cards with health-dot indicators
- Removed MQTT tab from settings modal
This commit is contained in:
2026-03-31 18:02:19 +03:00
parent e7c9a568dc
commit c59107c7c7
26 changed files with 1636 additions and 124 deletions
+18 -3
View File
@@ -49,6 +49,8 @@ from wled_controller.core.game_integration.event_bus import GameEventBus
import wled_controller.core.game_integration.adapters # noqa: F401 — register built-in adapters
from wled_controller.core.game_integration.community_loader import register_community_adapters
from wled_controller.core.mqtt.mqtt_service import MQTTService
from wled_controller.core.mqtt.mqtt_manager import MQTTManager
from wled_controller.storage.mqtt_source_store import MQTTSourceStore
from wled_controller.core.devices.mqtt_client import set_mqtt_service
from wled_controller.core.backup.auto_backup import AutoBackupEngine
from wled_controller.core.processing.os_notification_listener import OsNotificationListener
@@ -101,6 +103,7 @@ sync_clock_manager = SyncClockManager(sync_clock_store)
weather_manager = WeatherManager(weather_source_store)
ha_store = HomeAssistantStore(db)
ha_manager = HomeAssistantManager(ha_store)
mqtt_source_store = MQTTSourceStore(db)
game_integration_store = GameIntegrationStore(db)
game_event_bus = GameEventBus()
register_community_adapters()
@@ -158,11 +161,14 @@ async def lifespan(app: FastAPI):
client_labels = ", ".join(config.auth.api_keys.keys())
logger.info(f"Authorized clients: {client_labels}")
# Create MQTT service (shared broker connection)
# Create MQTT service (shared broker connection — legacy, used by MQTTLEDClient)
mqtt_service = MQTTService(config.mqtt)
set_mqtt_service(mqtt_service)
# Create automation engine (needs processor_manager + mqtt_service + stores for scene activation)
# Create MQTT manager (multi-source, ref-counted — new entity-based model)
mqtt_manager = MQTTManager(mqtt_source_store)
# Create automation engine (needs processor_manager + mqtt + stores for scene activation)
automation_engine = AutomationEngine(
automation_store,
processor_manager,
@@ -171,6 +177,7 @@ async def lifespan(app: FastAPI):
target_store=output_target_store,
device_store=device_store,
ha_manager=ha_manager,
mqtt_manager=mqtt_manager,
)
# Create auto-backup engine — derive paths from database location so that
@@ -222,6 +229,8 @@ async def lifespan(app: FastAPI):
ha_manager=ha_manager,
game_integration_store=game_integration_store,
game_event_bus=game_event_bus,
mqtt_store=mqtt_source_store,
mqtt_manager=mqtt_manager,
)
# Register devices in processor manager for health monitoring
@@ -332,7 +341,13 @@ async def lifespan(app: FastAPI):
except Exception as e:
logger.error(f"Error stopping processors: {e}")
# Stop MQTT service
# Stop MQTT manager (entity-based broker connections)
try:
await mqtt_manager.shutdown()
except Exception as e:
logger.error(f"Error stopping MQTT manager: {e}")
# Stop MQTT service (legacy global connection)
try:
await mqtt_service.stop()
except Exception as e: