# -*- coding: utf-8 -*- # hub/ticket/services/service_manager.py """Единый hardware gateway Ticket поверх serial/mock transport-сервисов.""" from __future__ import annotations from PySide6.QtCore import QObject, QTimer, Signal from domain import TicketHardwareStatus from .base_service import BaseService from .hardware_gateway import TicketHardwareObserver from .mock_service import MockService from .serial_service import SERIAL_BAUDRATE, SERIAL_PORT, SerialService, probe_serial_port PROBE_INTERVAL_SEC = 3 class ServiceManager(QObject): """Канонический hardware gateway Ticket.""" service_changed = Signal(str, bool) action_triggered = Signal(object) error_occurred = Signal(str) com_status_changed = Signal(bool, str) buttons_initialized = Signal(bool, int) port_disconnected = Signal() def __init__( self, serial_port: str = SERIAL_PORT, serial_baudrate: int = SERIAL_BAUDRATE, probe_interval_sec: int = PROBE_INTERVAL_SEC, parent: QObject | None = None, ): super().__init__(parent) self._serial_port = serial_port self._serial_baudrate = serial_baudrate self._observer: TicketHardwareObserver | None = None self._current_service: BaseService | None = None self._current_service_name = "none" self._button_states: dict[int, int] = {} self._status = TicketHardwareStatus() self._probe_timer = QTimer(self) self._probe_timer.setInterval(probe_interval_sec * 1000) self._probe_timer.timeout.connect(self._on_probe_timer) def start(self) -> None: if self._current_service is not None: return if probe_serial_port(self._serial_port): self._start_serial_service() else: self._start_mock_service() def stop(self) -> None: self._probe_timer.stop() self._shutdown_current_service() def get_status(self) -> TicketHardwareStatus: return self._status def set_observer(self, observer: TicketHardwareObserver | None) -> None: self._observer = observer if observer is not None: observer.on_gateway_status(self._status) def set_button_state(self, button_id: int, state_code: int) -> None: self._button_states[int(button_id)] = int(state_code) if self._current_service is not None: self._current_service.set_button_state(button_id, state_code) def remove_button_state(self, button_id: int) -> None: self._button_states.pop(int(button_id), None) if self._current_service is not None: self._current_service.remove_button_state(button_id) def reset_button_states(self) -> None: self._button_states.clear() if self._current_service is not None: self._current_service.reset_button_states() def _start_serial_service(self) -> None: service = SerialService( port=self._serial_port, baudrate=self._serial_baudrate, parent=self, ) if not self._activate_service(service, "serial", True): self._start_mock_service() return self._probe_timer.stop() def _start_mock_service(self) -> None: service = MockService(parent=self) self._activate_service(service, "mock", False) self._probe_timer.start() def _activate_service( self, service: BaseService, service_name: str, is_connected: bool, ) -> bool: self._shutdown_current_service() self._connect_service_signals(service) for button_id, state_code in self._button_states.items(): service.set_button_state(button_id, state_code) service.start() if not service.is_running() and service_name == "serial": service.deleteLater() return False self._current_service = service self._current_service_name = service_name self._refresh_status() self.service_changed.emit(service_name, is_connected) return True def _connect_service_signals(self, service: BaseService) -> None: service.action_triggered.connect(self._on_action_triggered) service.error_occurred.connect(self._on_error_occurred) service.com_status_changed.connect(self._on_com_status_changed) service.buttons_initialized.connect(self._on_buttons_initialized) service.port_disconnected.connect(self._on_port_disconnected) def _shutdown_current_service(self) -> None: if self._current_service is None: return current_service = self._current_service self._current_service = None self._current_service_name = "none" current_service.stop() current_service.deleteLater() self._status = TicketHardwareStatus() self._notify_status_observer() def _refresh_status(self) -> None: if self._current_service is None: self._status = TicketHardwareStatus() else: self._status = self._current_service.get_status() self._notify_status_observer() def _notify_status_observer(self) -> None: if self._observer is not None: self._observer.on_gateway_status(self._status) def _on_action_triggered(self, raw_action: object) -> None: self.action_triggered.emit(raw_action) if self._observer is not None and isinstance(raw_action, dict): self._observer.on_task_action(raw_action) def _on_error_occurred(self, message: str) -> None: self.error_occurred.emit(message) if self._observer is not None: self._observer.on_gateway_error(message) def _on_com_status_changed(self, is_connected: bool, message: str) -> None: self._refresh_status() self.com_status_changed.emit(is_connected, message) def _on_buttons_initialized(self, is_initialized: bool, button_count: int) -> None: self._refresh_status() self.buttons_initialized.emit(is_initialized, button_count) def _on_port_disconnected(self) -> None: self.port_disconnected.emit() if self._current_service_name == "serial": self._start_mock_service() def _on_probe_timer(self) -> None: port_available = probe_serial_port(self._serial_port) if port_available and self._current_service_name == "mock": self._start_serial_service() elif not port_available and self._current_service_name == "serial": self._start_mock_service()