209 lines
9.5 KiB
Python
209 lines
9.5 KiB
Python
"""Встраиваемая секция «Тест оплаты и призов» — монетоприёмник и хопперы.
|
|
|
|
Виджет не управляет потоком опроса самостоятельно — принимает callable
|
|
для отправки команд и вызывается снаружи при поступлении info-строк.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
|
|
from PySide6.QtCore import QTimer
|
|
from PySide6.QtWidgets import (
|
|
QGroupBox,
|
|
QHBoxLayout,
|
|
QLabel,
|
|
QMessageBox,
|
|
QPushButton,
|
|
QVBoxLayout,
|
|
QWidget,
|
|
)
|
|
|
|
from ...ui.icons import get_icon_color, icon
|
|
|
|
|
|
class PaymentSection(QWidget):
|
|
"""Inline-секция оплаты и призов (для базового режима)."""
|
|
|
|
def __init__(
|
|
self,
|
|
send_raw: Callable[[str], None],
|
|
parent: QWidget | None = None,
|
|
) -> None:
|
|
super().__init__(parent)
|
|
self._send_raw = send_raw
|
|
self._auto_dispense_hopper: int = 0
|
|
self._auto_dispense_timer: QTimer | None = None
|
|
# Нужен доступ к digital-маске для автовыдачи — будет передаваться снаружи
|
|
self._last_digital_val: int = 0xFFFFFFFF
|
|
self._setup_ui()
|
|
|
|
# ── UI ────────────────────────────────────────────────────────────
|
|
|
|
def _setup_ui(self) -> None:
|
|
root = QVBoxLayout(self)
|
|
root.setContentsMargins(0, 0, 0, 0)
|
|
root.setSpacing(10)
|
|
|
|
# ── Монетоприёмник ──
|
|
coin_group = QGroupBox("Монетоприёмник")
|
|
coin_layout = QHBoxLayout(coin_group)
|
|
self._coin_label = QLabel("Монеты: —")
|
|
self._coin_label.setMinimumWidth(120)
|
|
coin_layout.addWidget(self._coin_label)
|
|
self._coin_confirm_btn = QPushButton("Подтвердить (10)")
|
|
self._coin_confirm_btn.setIcon(icon("fa5s.check-circle", color=get_icon_color()))
|
|
self._coin_confirm_btn.setToolTip("Отправить подтверждение COIN (команда 10)")
|
|
self._coin_confirm_btn.clicked.connect(lambda: self._send_raw("10"))
|
|
coin_layout.addWidget(self._coin_confirm_btn)
|
|
coin_layout.addStretch()
|
|
root.addWidget(coin_group)
|
|
|
|
# ── Хопперы ──
|
|
hopper_group = QGroupBox("Хопперы")
|
|
hopper_layout = QVBoxLayout(hopper_group)
|
|
|
|
# Хоппер 1
|
|
h1_row = QHBoxLayout()
|
|
h1_row.addWidget(QLabel("Хоппер 1:"))
|
|
self._hopper1_label = QLabel("—")
|
|
self._hopper1_label.setMinimumWidth(100)
|
|
h1_row.addWidget(self._hopper1_label)
|
|
self._h1_on = QPushButton("Вкл")
|
|
self._h1_on.setIcon(icon("fa5s.play", color=get_icon_color()))
|
|
self._h1_on.setToolTip("Включить мотор (301)")
|
|
self._h1_on.clicked.connect(lambda: self._send_raw("301"))
|
|
h1_row.addWidget(self._h1_on)
|
|
self._h1_off = QPushButton("Выкл")
|
|
self._h1_off.setIcon(icon("fa5s.stop", color=get_icon_color()))
|
|
self._h1_off.setToolTip("Выключить мотор (300)")
|
|
self._h1_off.clicked.connect(lambda: self._send_raw("300"))
|
|
h1_row.addWidget(self._h1_off)
|
|
self._h1_auto_btn = QPushButton("Автовыдача")
|
|
self._h1_auto_btn.setIcon(icon("fa5s.cog", color=get_icon_color()))
|
|
self._h1_auto_btn.setToolTip("Программная автовыдача: вкл мотор → ждёт датчик (10 сек)")
|
|
self._h1_auto_btn.clicked.connect(lambda: self._start_auto_dispense(1))
|
|
h1_row.addWidget(self._h1_auto_btn)
|
|
h1_row.addStretch()
|
|
hopper_layout.addLayout(h1_row)
|
|
|
|
# Хоппер 2
|
|
h2_row = QHBoxLayout()
|
|
h2_row.addWidget(QLabel("Хоппер 2:"))
|
|
self._hopper2_label = QLabel("—")
|
|
self._hopper2_label.setMinimumWidth(100)
|
|
h2_row.addWidget(self._hopper2_label)
|
|
self._h2_on = QPushButton("Вкл")
|
|
self._h2_on.setIcon(icon("fa5s.play", color=get_icon_color()))
|
|
self._h2_on.setToolTip("Включить мотор (401)")
|
|
self._h2_on.clicked.connect(lambda: self._send_raw("401"))
|
|
h2_row.addWidget(self._h2_on)
|
|
self._h2_off = QPushButton("Выкл")
|
|
self._h2_off.setIcon(icon("fa5s.stop", color=get_icon_color()))
|
|
self._h2_off.setToolTip("Выключить мотор (400)")
|
|
self._h2_off.clicked.connect(lambda: self._send_raw("400"))
|
|
h2_row.addWidget(self._h2_off)
|
|
self._h2_auto_btn = QPushButton("Автовыдача")
|
|
self._h2_auto_btn.setIcon(icon("fa5s.cog", color=get_icon_color()))
|
|
self._h2_auto_btn.setToolTip("Программная автовыдача: вкл мотор → ждёт датчик (10 сек)")
|
|
self._h2_auto_btn.clicked.connect(lambda: self._start_auto_dispense(2))
|
|
h2_row.addWidget(self._h2_auto_btn)
|
|
h2_row.addStretch()
|
|
hopper_layout.addLayout(h2_row)
|
|
|
|
root.addWidget(hopper_group)
|
|
|
|
# ── Лог событий ──
|
|
self._event_log = QLabel("")
|
|
self._event_log.setWordWrap(True)
|
|
self._event_log.setStyleSheet("color: #888; font-size: 11px;")
|
|
root.addWidget(self._event_log)
|
|
|
|
# ── Public ────────────────────────────────────────────────────────
|
|
|
|
def refresh_icons(self) -> None:
|
|
clr = get_icon_color()
|
|
self._coin_confirm_btn.setIcon(icon("fa5s.check-circle", color=clr))
|
|
self._h1_on.setIcon(icon("fa5s.play", color=clr))
|
|
self._h1_off.setIcon(icon("fa5s.stop", color=clr))
|
|
self._h1_auto_btn.setIcon(icon("fa5s.cog", color=clr))
|
|
self._h2_on.setIcon(icon("fa5s.play", color=clr))
|
|
self._h2_off.setIcon(icon("fa5s.stop", color=clr))
|
|
self._h2_auto_btn.setIcon(icon("fa5s.cog", color=clr))
|
|
|
|
def on_info_line(self, line: str) -> None:
|
|
"""Обработать информационную строку (COINS, HOPPER, PRIZES)."""
|
|
upper = line.upper()
|
|
if upper.startswith("COINS"):
|
|
self._coin_label.setText(f"Монеты: {line}")
|
|
elif upper.startswith("HOPPER1"):
|
|
self._hopper1_label.setText(line)
|
|
elif upper.startswith("HOPPER2"):
|
|
self._hopper2_label.setText(line)
|
|
elif upper.startswith("HOPPER"):
|
|
self._hopper1_label.setText(line)
|
|
elif upper.startswith("PRIZES"):
|
|
self._coin_label.setText(f"Монеты: {line}")
|
|
self._event_log.setText(f"← {line}")
|
|
|
|
def on_digital(self, val: int) -> None:
|
|
"""Получить текущее значение цифровых входов (для автовыдачи)."""
|
|
self._last_digital_val = val
|
|
self._check_auto_dispense(val)
|
|
|
|
# ── Автовыдача ────────────────────────────────────────────────────
|
|
|
|
def _start_auto_dispense(self, hopper: int) -> None:
|
|
if self._auto_dispense_hopper != 0:
|
|
return
|
|
self._auto_dispense_hopper = hopper
|
|
label = self._hopper1_label if hopper == 1 else self._hopper2_label
|
|
btn = self._h1_auto_btn if hopper == 1 else self._h2_auto_btn
|
|
label.setText("Автовыдача…")
|
|
btn.setEnabled(False)
|
|
on_cmd = "301" if hopper == 1 else "401"
|
|
self._send_raw(on_cmd)
|
|
self._auto_dispense_timer = QTimer(self)
|
|
self._auto_dispense_timer.setSingleShot(True)
|
|
self._auto_dispense_timer.timeout.connect(self._auto_dispense_timeout)
|
|
self._auto_dispense_timer.start(10_000)
|
|
|
|
def _check_auto_dispense(self, val: int) -> None:
|
|
hopper = self._auto_dispense_hopper
|
|
if hopper == 0:
|
|
return
|
|
bit_index = 23 + hopper
|
|
sensor_active = not bool(val & (1 << bit_index))
|
|
if sensor_active:
|
|
self._finish_auto_dispense(success=True)
|
|
|
|
def _auto_dispense_timeout(self) -> None:
|
|
if self._auto_dispense_hopper != 0:
|
|
self._finish_auto_dispense(success=False)
|
|
|
|
def _finish_auto_dispense(self, *, success: bool) -> None:
|
|
hopper = self._auto_dispense_hopper
|
|
if hopper == 0:
|
|
return
|
|
if self._auto_dispense_timer is not None:
|
|
self._auto_dispense_timer.stop()
|
|
self._auto_dispense_timer.deleteLater()
|
|
self._auto_dispense_timer = None
|
|
off_cmd = "300" if hopper == 1 else "400"
|
|
self._send_raw(off_cmd)
|
|
label = self._hopper1_label if hopper == 1 else self._hopper2_label
|
|
btn = self._h1_auto_btn if hopper == 1 else self._h2_auto_btn
|
|
self._auto_dispense_hopper = 0
|
|
btn.setEnabled(True)
|
|
if success:
|
|
label.setText("Автовыдача: OK ✓")
|
|
self._event_log.setText(f"← Хоппер {hopper}: приз выдан")
|
|
else:
|
|
label.setText("Автовыдача: ОШИБКА ✗")
|
|
self._event_log.setText(f"← Хоппер {hopper}: таймаут 10 сек")
|
|
QMessageBox.warning(
|
|
self,
|
|
f"Хоппер {hopper}",
|
|
f"Ошибка выдачи: датчик хоппера {hopper} не сработал за 10 секунд.",
|
|
)
|