- Добавлена поддержка выбора произвольного цвета (QColorDialog) с отправкой команды 8008RRRGGGBBB. - Реализована панель управления GPIO25/GPIO26 (команда 50) в output_test_dialog.py. - Добавлена поддержка direct-объектов и эффектов Бродяги морей (команда 81) в led_test_dialog.py. - Реализованы тесты матриц и direct-шкалы (команда 85) в led_test_dialog.py. - Создан новый диалог prize_showcase_dialog.py для ячеек призовой витрины (команда 25). - Добавлена кнопка «Витрина призов» в PRO-кнопки connection_tab.py. - В payment_section.py добавлен показ накопительного счетчика монет global_summa (команда 93).
255 lines
12 KiB
Python
255 lines
12 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 ...services.com_service import ComConnection
|
||
from ...ui.icons import get_icon_color, icon
|
||
|
||
|
||
class PaymentSection(QWidget):
|
||
"""Inline-секция оплаты и призов (для базового режима)."""
|
||
|
||
def __init__(
|
||
self,
|
||
connection: ComConnection,
|
||
send_raw: Callable[[str], None],
|
||
parent: QWidget | None = None,
|
||
) -> None:
|
||
super().__init__(parent)
|
||
self._conn = connection
|
||
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)
|
||
|
||
desc = QLabel(
|
||
"Проверяет приём монет и выдачу призов. "
|
||
"Внесите монету, подтвердите оплату и проверьте работу хопперов. "
|
||
"При автовыдаче приз должен выдаться без ручного отключения мотора."
|
||
)
|
||
desc.setWordWrap(True)
|
||
desc.setObjectName("HelpText")
|
||
root.addWidget(desc)
|
||
|
||
# ── Монетоприёмник ──
|
||
coin_group = QGroupBox("Монетоприёмник")
|
||
coin_root_layout = QVBoxLayout(coin_group)
|
||
|
||
coin_row = QHBoxLayout()
|
||
self._coin_label = QLabel("Монеты: —")
|
||
self._coin_label.setMinimumWidth(120)
|
||
coin_row.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_row.addWidget(self._coin_confirm_btn)
|
||
coin_row.addStretch()
|
||
coin_root_layout.addLayout(coin_row)
|
||
|
||
global_row = QHBoxLayout()
|
||
self._global_summa_label = QLabel("Накопительный счетчик (cmd 93): —")
|
||
global_row.addWidget(self._global_summa_label)
|
||
|
||
self._global_summa_refresh_btn = QPushButton("Запросить")
|
||
self._global_summa_refresh_btn.setIcon(icon("fa5s.sync-alt", color=get_icon_color()))
|
||
self._global_summa_refresh_btn.setToolTip("Запросить global_summa")
|
||
self._global_summa_refresh_btn.clicked.connect(self._refresh_global_summa)
|
||
global_row.addWidget(self._global_summa_refresh_btn)
|
||
global_row.addStretch()
|
||
coin_root_layout.addLayout(global_row)
|
||
|
||
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._global_summa_refresh_btn.setIcon(icon("fa5s.sync-alt", 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 секунд.",
|
||
)
|
||
|
||
def _refresh_global_summa(self) -> None:
|
||
if not self._conn.is_connected:
|
||
QMessageBox.warning(self, "Ошибка", "Порт не подключён.")
|
||
return
|
||
try:
|
||
self._conn.send_command("93")
|
||
resp = self._conn.read_response(timeout=0.4)
|
||
self._conn.release()
|
||
if resp:
|
||
self._global_summa_label.setText(f"Накопительный счетчик (cmd 93): {resp}")
|
||
self._event_log.setText(f"← {resp}")
|
||
else:
|
||
self._global_summa_label.setText("Накопительный счетчик (cmd 93): нет ответа")
|
||
except Exception as exc:
|
||
self._conn.release()
|
||
QMessageBox.critical(self, "Ошибка", f"Не удалось прочитать счетчик: {exc}")
|