import logging import time from typing import Any from app.config import settings from app.mqtt.init_dispatcher import InitTaskDispatcher from app.mqtt.client import MqttService from app.services.init_registry import InitRegistry logger = logging.getLogger(__name__) class AutoInitService: def __init__( self, registry: InitRegistry, dispatcher: InitTaskDispatcher, mqtt: MqttService, ) -> None: self.registry = registry self.dispatcher = dispatcher self.mqtt = mqtt def _run_full_init(self, device_id: str) -> list[dict[str, Any]]: commands = self.dispatcher.dispatch_device_init(device_id) logger.info("Device init dispatched for %s (%d commands)", device_id, len(commands)) course_commands = self.dispatcher.dispatch_base_courses_sync(device_id) commands.extend(course_commands) logger.info( "Base courses sync dispatched for %s (%d courseTask messages)", device_id, len(course_commands), ) self.registry.update_record( device_id, courses_synced=True, courses_sync_at=int(time.time()), courses_sync_count=len(course_commands), ) return commands def on_device_state(self, device_id: str, payload: dict[str, Any], timestamp: int) -> list[dict[str, Any]] | None: if not settings.auto_init_enabled: return None if self.registry.is_initialized(device_id): return None if not self.mqtt.connected: logger.warning("Skip auto-init for %s: MQTT not connected", device_id) return None mode = payload.get("mode") if not self.registry.try_claim(device_id, source="auto", mode=mode, timestamp=timestamp): return None try: commands = self._run_full_init(device_id) logger.info("Auto-init completed for device %s (total %d commands)", device_id, len(commands)) return commands except Exception: self.registry.remove(device_id) logger.exception("Auto-init failed for device %s, registry entry rolled back", device_id) raise def dispatch_manual_init(self, device_id: str, force: bool = False) -> tuple[list[dict[str, Any]], str]: if self.registry.is_initialized(device_id) and not force: return [], "already_initialized" if force: self.registry.remove(device_id) if not self.registry.try_claim(device_id, source="manual"): return [], "already_initialized" try: commands = self._run_full_init(device_id) return commands, "dispatched" except Exception: self.registry.remove(device_id) raise