Add Dispatch_V0.1.1
This commit is contained in:
23
Dispatch_V0.1.1/services/__init__.py
Normal file
23
Dispatch_V0.1.1/services/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# hub/ticket/services/__init__.py
|
||||
|
||||
"""Публичный сервисный контур Ticket."""
|
||||
|
||||
from .base_service import BaseService
|
||||
from .hardware_gateway import TicketHardwareGateway, TicketHardwareObserver
|
||||
from .mock_service import MockService
|
||||
from .serial_service import SERIAL_BAUDRATE, SERIAL_PORT, SERIAL_TIMEOUT, SerialService, probe_serial_port
|
||||
from .service_manager import ServiceManager
|
||||
|
||||
__all__ = [
|
||||
"BaseService",
|
||||
"MockService",
|
||||
"SERIAL_BAUDRATE",
|
||||
"SERIAL_PORT",
|
||||
"SERIAL_TIMEOUT",
|
||||
"SerialService",
|
||||
"ServiceManager",
|
||||
"TicketHardwareGateway",
|
||||
"TicketHardwareObserver",
|
||||
"probe_serial_port",
|
||||
]
|
||||
BIN
Dispatch_V0.1.1/services/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
Dispatch_V0.1.1/services/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
93
Dispatch_V0.1.1/services/base_service.py
Normal file
93
Dispatch_V0.1.1/services/base_service.py
Normal file
@@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# hub/ticket/services/base_service.py
|
||||
|
||||
"""Базовый транспортный сервис Ticket без доменной логики."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import replace
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
|
||||
from domain import TicketConnectionStatus, TicketHardwareStatus
|
||||
from domain.ticket_constants import STATE_TODO
|
||||
|
||||
|
||||
class BaseService(QObject):
|
||||
"""Общий контракт transport-сервисов Ticket."""
|
||||
|
||||
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, parent: QObject | None = None):
|
||||
super().__init__(parent)
|
||||
self._button_states: dict[int, int] = {}
|
||||
self._status = TicketHardwareStatus()
|
||||
|
||||
def start(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def stop(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def is_running(self) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_status(self) -> TicketHardwareStatus:
|
||||
"""Вернуть последний известный статус transport-сервиса."""
|
||||
return self._status
|
||||
|
||||
def set_button_state(self, button_id: int, state_code: int) -> None:
|
||||
"""Сохранить последнее известное состояние кнопки."""
|
||||
self._button_states[int(button_id)] = int(state_code)
|
||||
|
||||
def remove_button_state(self, button_id: int) -> None:
|
||||
"""Удалить состояние кнопки из локального transport-кэша."""
|
||||
self._button_states.pop(int(button_id), None)
|
||||
|
||||
def reset_button_states(self) -> None:
|
||||
"""Сбросить все известные состояния кнопок."""
|
||||
self._button_states.clear()
|
||||
|
||||
def _get_button_state(self, button_id: int) -> int:
|
||||
return self._button_states.get(int(button_id), STATE_TODO)
|
||||
|
||||
def _set_connection_status(
|
||||
self,
|
||||
connection_status: TicketConnectionStatus,
|
||||
message: str,
|
||||
) -> None:
|
||||
if (
|
||||
self._status.connection_status == connection_status
|
||||
and self._status.message == message
|
||||
):
|
||||
return
|
||||
self._status = replace(
|
||||
self._status,
|
||||
connection_status=connection_status,
|
||||
message=message,
|
||||
)
|
||||
self.com_status_changed.emit(
|
||||
connection_status == TicketConnectionStatus.CONNECTED,
|
||||
message,
|
||||
)
|
||||
|
||||
def _set_button_initialization(
|
||||
self,
|
||||
is_initialized: bool,
|
||||
button_count: int,
|
||||
) -> None:
|
||||
if (
|
||||
self._status.buttons_initialized == is_initialized
|
||||
and self._status.button_count == button_count
|
||||
):
|
||||
return
|
||||
self._status = replace(
|
||||
self._status,
|
||||
buttons_initialized=is_initialized,
|
||||
button_count=button_count,
|
||||
)
|
||||
self.buttons_initialized.emit(is_initialized, button_count)
|
||||
48
Dispatch_V0.1.1/services/hardware_gateway.py
Normal file
48
Dispatch_V0.1.1/services/hardware_gateway.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# hub/ticket/services/hardware_gateway.py
|
||||
|
||||
"""Публичный контракт аппаратного шлюза Ticket."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping, Protocol
|
||||
|
||||
from domain import TicketHardwareStatus
|
||||
|
||||
|
||||
class TicketHardwareObserver(Protocol):
|
||||
"""Получатель событий от аппаратного или mock-шлюза."""
|
||||
|
||||
def on_task_action(self, raw_action: Mapping[str, Any]) -> None:
|
||||
"""Принять внешнее действие по задаче."""
|
||||
|
||||
def on_gateway_status(self, status: TicketHardwareStatus) -> None:
|
||||
"""Принять обновление статуса подключения и инициализации."""
|
||||
|
||||
def on_gateway_error(self, message: str) -> None:
|
||||
"""Принять сообщение об ошибке шлюза."""
|
||||
|
||||
|
||||
class TicketHardwareGateway(Protocol):
|
||||
"""Минимальный публичный API сервиса ввода событий Ticket."""
|
||||
|
||||
def start(self) -> None:
|
||||
"""Запустить внешний источник событий."""
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Остановить внешний источник событий."""
|
||||
|
||||
def get_status(self) -> TicketHardwareStatus:
|
||||
"""Вернуть текущий статус шлюза."""
|
||||
|
||||
def set_observer(self, observer: TicketHardwareObserver | None) -> None:
|
||||
"""Назначить получателя событий приложения."""
|
||||
|
||||
def set_button_state(self, button_id: int, state_code: int) -> None:
|
||||
"""Синхронизировать каноническое состояние кнопки для аппаратного шлюза."""
|
||||
|
||||
def remove_button_state(self, button_id: int) -> None:
|
||||
"""Удалить известное состояние кнопки из шлюза."""
|
||||
|
||||
def reset_button_states(self) -> None:
|
||||
"""Сбросить все известные состояния кнопок при смене контекста."""
|
||||
33
Dispatch_V0.1.1/services/mock_service.py
Normal file
33
Dispatch_V0.1.1/services/mock_service.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# hub/ticket/services/mock_service.py
|
||||
|
||||
"""Offline transport-сервис Ticket."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import QObject
|
||||
|
||||
from domain import TicketConnectionStatus
|
||||
from .base_service import BaseService
|
||||
|
||||
|
||||
class MockService(BaseService):
|
||||
"""Безопасный offline-режим при недоступном COM-порте."""
|
||||
|
||||
def __init__(self, parent: QObject | None = None):
|
||||
super().__init__(parent)
|
||||
self._running = False
|
||||
|
||||
def start(self) -> None:
|
||||
self._running = True
|
||||
self._set_connection_status(
|
||||
TicketConnectionStatus.DISCONNECTED,
|
||||
"Оффлайн-режим",
|
||||
)
|
||||
self._set_button_initialization(False, 0)
|
||||
|
||||
def stop(self) -> None:
|
||||
self._running = False
|
||||
|
||||
def is_running(self) -> bool:
|
||||
return self._running
|
||||
316
Dispatch_V0.1.1/services/serial_service.py
Normal file
316
Dispatch_V0.1.1/services/serial_service.py
Normal file
@@ -0,0 +1,316 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# hub/ticket/services/serial_service.py
|
||||
|
||||
"""Serial transport-сервис Ticket с подтверждением состояний кнопок."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from PySide6.QtCore import QObject
|
||||
|
||||
from error_logger import log_exception
|
||||
|
||||
from domain import TicketConnectionStatus
|
||||
from domain.ticket_constants import HARDWARE_SIGNAL_INITIALIZE
|
||||
from .base_service import BaseService
|
||||
|
||||
try:
|
||||
import serial
|
||||
from serial.tools import list_ports
|
||||
except ImportError:
|
||||
serial = None
|
||||
list_ports = None
|
||||
SERIAL_PORT = "COM21"
|
||||
SERIAL_BAUDRATE = 9600
|
||||
SERIAL_TIMEOUT = 0.1
|
||||
def probe_serial_port(port: str) -> bool:
|
||||
"""Проверить доступность COM-порта без запуска transport-потока."""
|
||||
if serial is None or list_ports is None:
|
||||
return False
|
||||
try:
|
||||
available_ports = {item.device for item in list_ports.comports()}
|
||||
except Exception as exc:
|
||||
log_exception(__name__, "probe_serial_port", exc)
|
||||
return False
|
||||
if port not in available_ports:
|
||||
return False
|
||||
serial_port = None
|
||||
try:
|
||||
serial_port = serial.Serial(port=port, baudrate=SERIAL_BAUDRATE, timeout=SERIAL_TIMEOUT)
|
||||
return True
|
||||
except Exception as exc:
|
||||
log_exception(__name__, "probe_serial_port.serial_open", exc)
|
||||
return False
|
||||
finally:
|
||||
if serial_port is not None and serial_port.is_open:
|
||||
serial_port.close()
|
||||
class SerialService(BaseService):
|
||||
"""Transport-сервис чтения и записи пакетов COM-порта."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
port: str = SERIAL_PORT,
|
||||
baudrate: int = SERIAL_BAUDRATE,
|
||||
timeout: float = SERIAL_TIMEOUT,
|
||||
resend_interval_sec: float = 1.0,
|
||||
reconnect_delay_sec: float = 1.0,
|
||||
max_connect_attempts: int = 3,
|
||||
parent: QObject | None = None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
self._port_name = port
|
||||
self._baudrate = baudrate
|
||||
self._timeout = timeout
|
||||
self._resend_interval_sec = resend_interval_sec
|
||||
self._reconnect_delay_sec = reconnect_delay_sec
|
||||
self._max_connect_attempts = max_connect_attempts
|
||||
self._lock = threading.Lock()
|
||||
self._stop_event = threading.Event()
|
||||
self._thread: threading.Thread | None = None
|
||||
self._serial_port: Any | None = None
|
||||
self._buffer = bytearray()
|
||||
self._initialized_buttons: set[int] = set()
|
||||
self._pending_states: dict[int, int] = {}
|
||||
self._retry_deadlines: dict[int, float] = {}
|
||||
|
||||
def start(self) -> None:
|
||||
if self.is_running():
|
||||
return
|
||||
if serial is None:
|
||||
message = "pyserial недоступен, serial transport не может быть запущен."
|
||||
self.error_occurred.emit(message)
|
||||
self._set_connection_status(TicketConnectionStatus.ERROR, message)
|
||||
self._set_button_initialization(False, 0)
|
||||
return
|
||||
self._stop_event.clear()
|
||||
self._thread = threading.Thread(target=self._run_loop, name="TicketSerialService", daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
def stop(self) -> None:
|
||||
self._stop_event.set()
|
||||
self._close_port("Порт закрыт")
|
||||
if self._thread is not None:
|
||||
self._thread.join(timeout=1.0)
|
||||
self._thread = None
|
||||
|
||||
def is_running(self) -> bool:
|
||||
return self._thread is not None and self._thread.is_alive()
|
||||
|
||||
def set_button_state(self, button_id: int, state_code: int) -> None:
|
||||
super().set_button_state(button_id, state_code)
|
||||
with self._lock:
|
||||
button_id = int(button_id)
|
||||
state_code = int(state_code)
|
||||
self._pending_states[button_id] = state_code
|
||||
serial_port = self._serial_port
|
||||
port_open = serial_port is not None and serial_port.is_open
|
||||
is_initialized = button_id in self._initialized_buttons
|
||||
if port_open:
|
||||
self._send_state(button_id, state_code, schedule_retry=is_initialized)
|
||||
|
||||
def remove_button_state(self, button_id: int) -> None:
|
||||
super().remove_button_state(button_id)
|
||||
with self._lock:
|
||||
button_id = int(button_id)
|
||||
self._pending_states.pop(button_id, None)
|
||||
self._retry_deadlines.pop(button_id, None)
|
||||
|
||||
def reset_button_states(self) -> None:
|
||||
super().reset_button_states()
|
||||
with self._lock:
|
||||
self._pending_states.clear()
|
||||
self._retry_deadlines.clear()
|
||||
|
||||
def _run_loop(self) -> None:
|
||||
attempts = 0
|
||||
while not self._stop_event.is_set():
|
||||
if not self._is_port_open():
|
||||
if self._connect_port():
|
||||
attempts = 0
|
||||
self._flush_known_states()
|
||||
else:
|
||||
attempts += 1
|
||||
if attempts >= self._max_connect_attempts:
|
||||
try:
|
||||
self.error_occurred.emit(
|
||||
f"Не удалось подключиться к {self._port_name}."
|
||||
)
|
||||
self.port_disconnected.emit()
|
||||
except RuntimeError:
|
||||
pass
|
||||
return
|
||||
self._stop_event.wait(self._reconnect_delay_sec)
|
||||
continue
|
||||
try:
|
||||
self._read_available_packets()
|
||||
self._retry_pending_states_if_needed()
|
||||
except Exception as exc:
|
||||
log_exception(__name__, "SerialService._run_loop", exc)
|
||||
try:
|
||||
self.port_disconnected.emit()
|
||||
except RuntimeError:
|
||||
return
|
||||
self._close_port("Порт закрыт")
|
||||
self._stop_event.wait(self._reconnect_delay_sec)
|
||||
continue
|
||||
self._stop_event.wait(0.05)
|
||||
|
||||
def _connect_port(self) -> bool:
|
||||
try:
|
||||
serial_port = serial.Serial(
|
||||
port=self._port_name,
|
||||
baudrate=self._baudrate,
|
||||
timeout=self._timeout,
|
||||
write_timeout=0.5,
|
||||
inter_byte_timeout=0.005,
|
||||
)
|
||||
serial_port.reset_input_buffer()
|
||||
serial_port.reset_output_buffer()
|
||||
except Exception as exc:
|
||||
log_exception(__name__, "SerialService._connect_port", exc)
|
||||
self._set_connection_status(
|
||||
TicketConnectionStatus.ERROR,
|
||||
f"Ошибка подключения к {self._port_name}: {exc}",
|
||||
)
|
||||
self._set_button_initialization(False, 0)
|
||||
return False
|
||||
with self._lock:
|
||||
self._serial_port = serial_port
|
||||
self._buffer.clear()
|
||||
self._set_connection_status(
|
||||
TicketConnectionStatus.CONNECTED,
|
||||
f"{self._port_name} ({self._baudrate} бод)",
|
||||
)
|
||||
self._set_button_initialization(False, 0)
|
||||
return True
|
||||
|
||||
def _read_available_packets(self) -> None:
|
||||
with self._lock:
|
||||
serial_port = self._serial_port
|
||||
if serial_port is None or not serial_port.is_open:
|
||||
return
|
||||
data_available = getattr(serial_port, "in_waiting", 0)
|
||||
if data_available <= 0:
|
||||
return
|
||||
payload = serial_port.read(data_available)
|
||||
if not payload:
|
||||
return
|
||||
with self._lock:
|
||||
self._buffer.extend(payload)
|
||||
self._process_buffer()
|
||||
|
||||
def _process_buffer(self) -> None:
|
||||
while True:
|
||||
with self._lock:
|
||||
if len(self._buffer) < 4:
|
||||
return
|
||||
packet_index = self._find_packet_start()
|
||||
if packet_index is None:
|
||||
if len(self._buffer) > 100:
|
||||
self._buffer.clear()
|
||||
return
|
||||
button_id = self._buffer[packet_index]
|
||||
hardware_state = self._buffer[packet_index + 1]
|
||||
del self._buffer[: packet_index + 4]
|
||||
self._handle_packet(button_id, hardware_state)
|
||||
|
||||
def _find_packet_start(self) -> int | None:
|
||||
buffer_length = len(self._buffer)
|
||||
for index in range(buffer_length - 3):
|
||||
if self._buffer[index + 2] == 0x0D and self._buffer[index + 3] == 0x0A:
|
||||
return index
|
||||
return None
|
||||
|
||||
def _handle_packet(self, button_id: int, hardware_state: int) -> None:
|
||||
if not 1 <= button_id <= 8:
|
||||
return
|
||||
if hardware_state == HARDWARE_SIGNAL_INITIALIZE:
|
||||
self._handle_initialization_request(button_id)
|
||||
return
|
||||
if hardware_state == 0xAA:
|
||||
self._handle_confirmation(button_id)
|
||||
return
|
||||
if hardware_state not in (0, 1, 2, 3):
|
||||
self.error_occurred.emit(
|
||||
f"Неизвестное аппаратное состояние: {hardware_state:02X}"
|
||||
)
|
||||
return
|
||||
self.action_triggered.emit(
|
||||
{
|
||||
"event": "advance",
|
||||
"button_id": button_id,
|
||||
"hardware_state": hardware_state,
|
||||
"current_state_code": self._get_button_state(button_id),
|
||||
}
|
||||
)
|
||||
|
||||
def _handle_initialization_request(self, button_id: int) -> None:
|
||||
with self._lock:
|
||||
self._initialized_buttons.add(button_id)
|
||||
button_count = len(self._initialized_buttons)
|
||||
self._set_button_initialization(True, button_count)
|
||||
self._send_state(button_id, self._get_button_state(button_id))
|
||||
|
||||
def _handle_confirmation(self, button_id: int) -> None:
|
||||
with self._lock:
|
||||
self._pending_states.pop(button_id, None)
|
||||
self._retry_deadlines.pop(button_id, None)
|
||||
|
||||
def _retry_pending_states_if_needed(self) -> None:
|
||||
now = time.monotonic()
|
||||
with self._lock:
|
||||
pending_items = list(self._retry_deadlines.items())
|
||||
for button_id, retry_deadline in pending_items:
|
||||
if retry_deadline <= now:
|
||||
self._send_state(button_id, self._get_button_state(button_id))
|
||||
|
||||
def _flush_known_states(self) -> None:
|
||||
"""Отправить все известные состояния кнопок однократно (без retry)."""
|
||||
for button_id, state_code in list(self._button_states.items()):
|
||||
self._send_state(button_id, state_code, schedule_retry=False)
|
||||
|
||||
def _send_state(self, button_id: int, state_code: int, *, schedule_retry: bool = True) -> bool:
|
||||
packet = bytes([int(button_id), int(state_code) & 0xFF, 0x0D, 0x0A])
|
||||
try:
|
||||
with self._lock:
|
||||
serial_port = self._serial_port
|
||||
if serial_port is None or not serial_port.is_open:
|
||||
return False
|
||||
serial_port.write(packet)
|
||||
serial_port.flush()
|
||||
if schedule_retry:
|
||||
self._pending_states[int(button_id)] = int(state_code)
|
||||
self._retry_deadlines[int(button_id)] = time.monotonic() + self._resend_interval_sec
|
||||
except Exception as exc:
|
||||
log_exception(__name__, "SerialService._send_state", exc)
|
||||
try:
|
||||
self.port_disconnected.emit()
|
||||
except RuntimeError:
|
||||
return False
|
||||
self._close_port("Порт закрыт")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _close_port(self, message: str) -> None:
|
||||
with self._lock:
|
||||
serial_port = self._serial_port
|
||||
self._serial_port = None
|
||||
self._buffer.clear()
|
||||
self._initialized_buttons.clear()
|
||||
self._pending_states.clear()
|
||||
self._retry_deadlines.clear()
|
||||
if serial_port is not None and serial_port.is_open:
|
||||
try:
|
||||
serial_port.close()
|
||||
except Exception as exc:
|
||||
log_exception(__name__, "SerialService._close_port", exc)
|
||||
self._set_button_initialization(False, 0)
|
||||
self._set_connection_status(TicketConnectionStatus.DISCONNECTED, message)
|
||||
|
||||
def _is_port_open(self) -> bool:
|
||||
with self._lock:
|
||||
serial_port = self._serial_port
|
||||
return serial_port is not None and serial_port.is_open
|
||||
177
Dispatch_V0.1.1/services/service_manager.py
Normal file
177
Dispatch_V0.1.1/services/service_manager.py
Normal file
@@ -0,0 +1,177 @@
|
||||
# -*- 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()
|
||||
Reference in New Issue
Block a user