Add board v2.x commands support to UI dialogs and sections

- Добавлена поддержка выбора произвольного цвета (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).
This commit is contained in:
Данил Ярош 2026-06-30 09:22:36 +03:00
parent 85a7b4db55
commit 448b9bb07a
6 changed files with 584 additions and 4 deletions

View File

@ -269,6 +269,7 @@ class ConnectionTab(QWidget):
self._button_backlight_section = ButtonBacklightSection(self._connection)
self._input_section = InputTestSection(self._connection)
self._payment_section = PaymentSection(
connection=self._connection,
send_raw=lambda cmd: self._input_section._send_raw(cmd),
)
@ -310,6 +311,7 @@ class ConnectionTab(QWidget):
("Тест силовых выходов", "fa5s.bolt", self._open_output_test),
("Обновление прошивки", "fa5s.upload", self._open_firmware),
("Задание роли", "fa5s.user-tag", self._open_role),
("Витрина призов", "fa5s.gift", self._open_prize_showcase),
)
self._pro_buttons: list[tuple[QPushButton, str]] = []
for title, icon_name, handler in pro_items:
@ -605,6 +607,13 @@ class ConnectionTab(QWidget):
lambda: RoleDialog(self._connection, parent=self),
)
def _open_prize_showcase(self) -> None:
from .dialogs.prize_showcase_dialog import PrizeShowcaseDialog
self._exec_dialog(
"prize_showcase",
lambda: PrizeShowcaseDialog(self._connection, parent=self),
)
def _open_terminal(self) -> None:
from .dialogs.command_terminal_dialog import CommandTerminalDialog
self._exec_dialog(

View File

@ -16,6 +16,7 @@ import sys
from PySide6.QtCore import Qt, QTimer
from PySide6.QtWidgets import (
QCheckBox,
QColorDialog,
QFormLayout,
QGroupBox,
QHBoxLayout,
@ -198,6 +199,12 @@ class LedTestDialog(StyledDialog):
gamma_layout.addWidget(apply_gamma_btn)
color_form.addRow("Гамма-коррекция:", gamma_layout)
# Пользовательский цвет
self._custom_color_btn = QPushButton("Выбрать и отправить цвет")
self._custom_color_btn.setIcon(icon("fa5s.palette", color=get_icon_color()))
self._custom_color_btn.clicked.connect(self._send_custom_color)
color_form.addRow("Свой цвет (08):", self._custom_color_btn)
# Тест цвета
self._color_test_btn = QPushButton("Тест цвета")
self._color_test_btn.setProperty("role", "primary")
@ -212,6 +219,159 @@ class LedTestDialog(StyledDialog):
root.addWidget(color_group)
# ── Бродяга морей (cmd 81) ──
wanderer_group = QGroupBox("Бродяга морей (cmd 81)")
wanderer_form = QFormLayout(wanderer_group)
wanderer_form.setSpacing(6)
# Объект 1
self._w1_level = QSpinBox()
self._w1_level.setRange(0, 21)
self._w1_level.setValue(5)
w1_apply_level = QPushButton("Задать уровень")
w1_apply_level.setIcon(icon("fa5s.angle-double-right", color=get_icon_color()))
w1_apply_level.clicked.connect(self._send_w1_level)
w1_level_layout = QHBoxLayout()
w1_level_layout.addWidget(self._w1_level, 1)
w1_level_layout.addWidget(w1_apply_level)
wanderer_form.addRow("Шкала 1 (уровень 0..21):", w1_level_layout)
self._w1_effect = NoWheelComboBox()
self._w1_effect.addItem("Эффект 1 (синий/фиолетовый)", 1)
self._w1_effect.addItem("Эффект 2 (фиолетовый/голубой)", 2)
self._w1_effect.addItem("Эффект 3 (мигание белым)", 3)
w1_apply_eff = QPushButton("Запустить эффект")
w1_apply_eff.setProperty("role", "primary")
w1_apply_eff.setIcon(icon("fa5s.play", color="#ecfdf5"))
w1_apply_eff.clicked.connect(self._send_w1_effect)
w1_eff_layout = QHBoxLayout()
w1_eff_layout.addWidget(self._w1_effect, 1)
w1_eff_layout.addWidget(w1_apply_eff)
wanderer_form.addRow("Эффекты шкалы 1:", w1_eff_layout)
# Объект 2
self._w2_level = QSpinBox()
self._w2_level.setRange(0, 44)
self._w2_level.setValue(5)
w2_apply_level = QPushButton("Задать уровень")
w2_apply_level.setIcon(icon("fa5s.angle-double-right", color=get_icon_color()))
w2_apply_level.clicked.connect(self._send_w2_level)
w2_level_layout = QHBoxLayout()
w2_level_layout.addWidget(self._w2_level, 1)
w2_level_layout.addWidget(w2_apply_level)
wanderer_form.addRow("Шкала 2 (уровень 0..44):", w2_level_layout)
self._w2_effect = NoWheelComboBox()
self._w2_effect.addItem("Эффект 1 (синий/фиолетовый)", 1)
self._w2_effect.addItem("Эффект 2 (фиолетовый/голубой)", 2)
self._w2_effect.addItem("Эффект 3 (мигание белым)", 3)
w2_apply_eff = QPushButton("Запустить эффект")
w2_apply_eff.setProperty("role", "primary")
w2_apply_eff.setIcon(icon("fa5s.play", color="#ecfdf5"))
w2_apply_eff.clicked.connect(self._send_w2_effect)
w2_eff_layout = QHBoxLayout()
w2_eff_layout.addWidget(self._w2_effect, 1)
w2_eff_layout.addWidget(w2_apply_eff)
wanderer_form.addRow("Эффекты шкалы 2:", w2_eff_layout)
# Сброс обоих
w_clear_btn = QPushButton("Очистить обе шкалы (8130)")
w_clear_btn.setIcon(icon("fa5s.eraser", color=get_icon_color()))
w_clear_btn.clicked.connect(self._clear_wanderer)
wanderer_form.addRow(w_clear_btn)
root.addWidget(wanderer_group)
# ── Управление виртуальной матрицей (cmd 85/86/87) ──
matrix_group = QGroupBox("Виртуальная матрица (cmd 85/86/87)")
matrix_form = QFormLayout(matrix_group)
matrix_form.setSpacing(6)
# Автокарусель / Рендеринг
carousel_layout = QHBoxLayout()
self._mc_on_btn = QPushButton("Карусель Вкл")
self._mc_on_btn.setIcon(icon("fa5s.play-circle", color=get_icon_color()))
self._mc_on_btn.clicked.connect(self._send_carousel_on)
carousel_layout.addWidget(self._mc_on_btn)
self._mc_off_btn = QPushButton("Карусель Выкл")
self._mc_off_btn.setIcon(icon("fa5s.stop-circle", color=get_icon_color()))
self._mc_off_btn.clicked.connect(self._send_carousel_off)
carousel_layout.addWidget(self._mc_off_btn)
self._mc_stop_btn = QPushButton("Rendering OFF")
self._mc_stop_btn.setProperty("role", "primary")
self._mc_stop_btn.setIcon(icon("fa5s.power-off", color="#ecfdf5"))
self._mc_stop_btn.clicked.connect(self._send_rendering_off)
carousel_layout.addWidget(self._mc_stop_btn)
matrix_form.addRow("Режимы рендера:", carousel_layout)
# Тесты матриц (8525 / 8625 / 8725)
test_type_layout = QHBoxLayout()
self._m_test_type = NoWheelComboBox()
self._m_test_type.addItem("Выключить тест (0)", 0)
self._m_test_type.addItem("Общий тест / Змейка (1)", 1)
self._m_test_type.addItem("Тест строки (2)", 2)
self._m_test_type.addItem("Тест столбца (3)", 3)
self._m_test_type.addItem("Тест пикселя (4)", 4)
self._m_test_type.currentIndexChanged.connect(self._on_matrix_test_type_changed)
test_type_layout.addWidget(self._m_test_type, 1)
self._m_test_param = QSpinBox()
self._m_test_param.setRange(0, 999)
self._m_test_param.setValue(0)
self._m_test_param.setVisible(False)
test_type_layout.addWidget(self._m_test_param)
m_test_btn = QPushButton("Запустить")
m_test_btn.setIcon(icon("fa5s.vial", color=get_icon_color()))
m_test_btn.clicked.connect(self._send_matrix_test)
test_type_layout.addWidget(m_test_btn)
matrix_form.addRow("Аппаратный тест:", test_type_layout)
# Direct-шкалы (только для s_b)
self._scales_container = QWidget()
scales_layout = QVBoxLayout(self._scales_container)
scales_layout.setContentsMargins(0, 0, 0, 0)
scales_layout.setSpacing(4)
scales_title = QLabel("<b>Direct-шкалы (только для Морского боя s_b)</b>")
scales_layout.addWidget(scales_title)
self._m_scales: list[QSpinBox] = []
for i in range(4):
scale_row = QHBoxLayout()
spin = QSpinBox()
spin.setRange(0, 22)
spin.setValue(0)
self._m_scales.append(spin)
scale_row.addWidget(spin, 1)
btn = QPushButton(f"Задать шкалу {i+1}")
btn.setIcon(icon("fa5s.level-up-alt", color=get_icon_color()))
btn.clicked.connect(lambda checked=False, idx=i: self._send_matrix_scale(idx))
scale_row.addWidget(btn)
scales_layout.addLayout(scale_row)
clear_scales_btn = QPushButton("Очистить direct-шкалы (8530)")
clear_scales_btn.setIcon(icon("fa5s.trash-alt", color=get_icon_color()))
clear_scales_btn.clicked.connect(self._clear_matrix_scales)
scales_layout.addWidget(clear_scales_btn)
matrix_form.addRow(self._scales_container)
root.addWidget(matrix_group)
# ── Лог ──
self._log = QLabel("")
self._log.setObjectName("LogLabel")
@ -257,6 +417,10 @@ class LedTestDialog(StyledDialog):
# Кнопка «выключить» — только для has_duration
self._stop_btn.setVisible(mode.has_duration)
# Показывать direct-шкалы только для Морского боя s_b (где base_command == "85")
is_sb = (mode.base_command == "85")
self._scales_container.setVisible(is_sb)
def _reload_modes(self) -> None:
"""Перечитать .lm файлы из папки."""
current_name = self._project_combo.currentText()
@ -338,6 +502,16 @@ class LedTestDialog(StyledDialog):
cmd = f"89{ch}2{val}"
self._send(cmd)
def _send_custom_color(self) -> None:
color = QColorDialog.getColor(Qt.GlobalColor.white, self, "Выберите цвет")
if color.isValid():
ch = self._cmd89_channel_combo.currentData()
r = color.red()
g = color.green()
b = color.blue()
cmd = f"8{ch}08{r:03d}{g:03d}{b:03d}"
self._send(cmd)
def _run_color_test(self) -> None:
"""Последовательно отправить 8003→8004→8005→8002→8000 с паузой 300 мс."""
self._color_test_cmds = ["8003", "8004", "8005", "8002", "8000"]
@ -359,6 +533,71 @@ class LedTestDialog(StyledDialog):
self._color_test_idx += 1
self._send(cmd)
def _send_w1_level(self) -> None:
val = self._w1_level.value()
cmd = f"8131{val:02d}"
self._send(cmd)
def _send_w1_effect(self) -> None:
val = self._w1_effect.currentData()
cmd = f"8131{val}"
self._send(cmd)
def _send_w2_level(self) -> None:
val = self._w2_level.value()
cmd = f"8132{val:02d}"
self._send(cmd)
def _send_w2_effect(self) -> None:
val = self._w2_effect.currentData()
cmd = f"8132{val}"
self._send(cmd)
def _clear_wanderer(self) -> None:
self._send("8130")
def _send_carousel_on(self) -> None:
mode = self._current_mode()
if mode:
self._send(f"{mode.base_command}01")
def _send_carousel_off(self) -> None:
mode = self._current_mode()
if mode:
self._send(f"{mode.base_command}00")
def _send_rendering_off(self) -> None:
mode = self._current_mode()
if mode:
self._send(mode.base_command)
def _on_matrix_test_type_changed(self, index: int) -> None:
test_type = self._m_test_type.itemData(index)
self._m_test_param.setVisible(test_type >= 2)
def _send_matrix_test(self) -> None:
mode = self._current_mode()
if not mode:
return
test_type = self._m_test_type.currentData()
if test_type == 0 or test_type == 1:
cmd = f"{mode.base_command}25{test_type}"
elif test_type == 2 or test_type == 3:
val = self._m_test_param.value()
cmd = f"{mode.base_command}25{test_type}{val:02d}"
elif test_type == 4:
val = self._m_test_param.value()
cmd = f"{mode.base_command}25{test_type}{val:03d}"
self._send(cmd)
def _send_matrix_scale(self, index: int) -> None:
val = self._m_scales[index].value()
cmd = f"853{index+1}{val:02d}"
self._send(cmd)
def _clear_matrix_scales(self) -> None:
self._send("8530")
def _send(self, cmd: str) -> None:
if not cmd:
return

View File

@ -9,6 +9,7 @@ from __future__ import annotations
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QFormLayout,
QGridLayout,
QGroupBox,
QHBoxLayout,
@ -22,6 +23,7 @@ from ...services.com_service import ComConnection
from ...services import settings_service
from ...ui.icons import get_icon_color, get_icon_muted, icon
from ...ui.theme import get_palette
from ..no_wheel_combo_box import NoWheelComboBox
from .styled_dialog import StyledDialog
_NUM_OUTPUTS = 8
@ -122,6 +124,67 @@ class OutputTestDialog(StyledDialog):
root.addLayout(bulk_layout)
# ── Дополнительные GPIO25/GPIO26 (cmd 50) ──
gpio_group = QGroupBox("Дополнительные GPIO25/GPIO26 (cmd 50)")
gpio_form = QFormLayout(gpio_group)
gpio_form.setSpacing(6)
# Выбор режимов
self._gpio25_mode = NoWheelComboBox()
self._gpio25_mode.addItem("Вход (Input)", 0)
self._gpio25_mode.addItem("Выход (Output)", 1)
self._gpio26_mode = NoWheelComboBox()
self._gpio26_mode.addItem("Вход (Input)", 0)
self._gpio26_mode.addItem("Выход (Output)", 1)
mode_layout = QHBoxLayout()
mode_layout.addWidget(self._gpio25_mode, 1)
mode_layout.addWidget(QLabel("GPIO26:"))
mode_layout.addWidget(self._gpio26_mode, 1)
apply_mode_btn = QPushButton("Задать режим")
apply_mode_btn.setIcon(icon("fa5s.cog", color=get_icon_color()))
apply_mode_btn.clicked.connect(self._send_gpio_modes)
mode_layout.addWidget(apply_mode_btn)
gpio_form.addRow("GPIO25 Режим:", mode_layout)
# Установка уровней
self._gpio25_level = NoWheelComboBox()
self._gpio25_level.addItem("Низкий (Low)", 0)
self._gpio25_level.addItem("Высокий (High)", 1)
self._gpio26_level = NoWheelComboBox()
self._gpio26_level.addItem("Низкий (Low)", 0)
self._gpio26_level.addItem("Высокий (High)", 1)
level_layout = QHBoxLayout()
level_layout.addWidget(self._gpio25_level, 1)
level_layout.addWidget(QLabel("GPIO26:"))
level_layout.addWidget(self._gpio26_level, 1)
apply_level_btn = QPushButton("Задать уровни")
apply_level_btn.setIcon(icon("fa5s.bolt", color=get_icon_color()))
apply_level_btn.clicked.connect(self._send_gpio_levels)
level_layout.addWidget(apply_level_btn)
gpio_form.addRow("GPIO25 Уровень:", level_layout)
# Запрос состояния
status_layout = QHBoxLayout()
read_state_btn = QPushButton("Запросить состояние (cmd 50)")
read_state_btn.setProperty("role", "primary")
read_state_btn.setIcon(icon("fa5s.sync-alt", color="#ecfdf5"))
read_state_btn.clicked.connect(self._read_gpio_state)
status_layout.addWidget(read_state_btn)
read_short_btn = QPushButton("Краткое состояние (cmd 5005)")
read_short_btn.setIcon(icon("fa5s.info-circle", color=get_icon_color()))
read_short_btn.clicked.connect(self._read_gpio_short)
status_layout.addWidget(read_short_btn)
gpio_form.addRow(status_layout)
root.addWidget(gpio_group)
# ── Лог ──
self._log = QLabel("")
self._log.setObjectName("LogLabel")
@ -156,6 +219,26 @@ class OutputTestDialog(StyledDialog):
cmd = "200"
self._send(cmd)
def _send_gpio_modes(self) -> None:
g25 = self._gpio25_mode.currentData()
g26 = self._gpio26_mode.currentData()
p_val = g25 * 10 + g26
cmd = f"5001{p_val:02d}"
self._send(cmd)
def _send_gpio_levels(self) -> None:
g25 = self._gpio25_level.currentData()
g26 = self._gpio26_level.currentData()
p_val = g25 * 10 + g26
cmd = f"5010{p_val:02d}"
self._send(cmd)
def _read_gpio_state(self) -> None:
self._send("50")
def _read_gpio_short(self) -> None:
self._send("50050")
def _send(self, cmd: str) -> None:
if not cmd:
return

View File

@ -0,0 +1,195 @@
"""Диалог «Управление призовой витриной» — ячейки призовой стойки (cmd 25).
Управляет количеством ячеек от 1 до 20 (по умолчанию 5).
Команды:
- 25: Показать состояние всех ячеек
- 2501B: Выдать приз из ячейки B
- 2511B: Открыть ячейку B без списания приза
- 25991/25990: Включить/выключить автозапуск и перезагрузить
"""
from __future__ import annotations
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QFormLayout,
QGridLayout,
QGroupBox,
QHBoxLayout,
QLabel,
QMessageBox,
QPushButton,
QScrollArea,
QSpinBox,
QVBoxLayout,
QWidget,
)
from ...services.com_service import ComConnection
from ...ui.icons import get_icon_color, get_icon_muted, icon
from .styled_dialog import StyledDialog
class PrizeShowcaseDialog(StyledDialog):
"""Диалог управления призовой витриной (cmd 25)."""
def __init__(self, connection: ComConnection, *, parent=None) -> None:
super().__init__(parent)
self._conn = connection
self.setWindowTitle("Призовая витрина")
self.setMinimumWidth(550)
self.setMinimumHeight(450)
self._build_ui()
def _build_ui(self) -> None:
root = self.containerLayout()
root.setSpacing(12)
# ── Описание ──
desc = QLabel(
"Управление ячейками витрины призов (команда 25).\n"
"Поддерживается выдача призов, разблокировка ячеек и управление авторежимом."
)
desc.setWordWrap(True)
desc.setStyleSheet("color: #9ca3af; font-size: 12px; margin-bottom: 4px;")
root.addWidget(desc)
# ── Общие настройки авторежима ──
global_group = QGroupBox("Общие параметры витрины")
global_layout = QHBoxLayout(global_group)
query_btn = QPushButton("Состояние ячеек (cmd 25)")
query_btn.setProperty("role", "primary")
query_btn.setIcon(icon("fa5s.sync-alt", color="#ecfdf5"))
query_btn.clicked.connect(self._query_state)
global_layout.addWidget(query_btn)
auto_on_btn = QPushButton("Авторежим Вкл")
auto_on_btn.setIcon(icon("fa5s.play-circle", color=get_icon_color()))
auto_on_btn.clicked.connect(self._send_auto_on)
global_layout.addWidget(auto_on_btn)
auto_off_btn = QPushButton("Авторежим Выкл")
auto_off_btn.setIcon(icon("fa5s.stop-circle", color=get_icon_color()))
auto_off_btn.clicked.connect(self._send_auto_off)
global_layout.addWidget(auto_off_btn)
root.addWidget(global_group)
# ── Выбор количества ячеек ──
cfg_layout = QHBoxLayout()
cfg_layout.addWidget(QLabel("Количество ячеек витрины (1-20):"))
self._cells_count_spin = QSpinBox()
self._cells_count_spin.setRange(1, 20)
self._cells_count_spin.setValue(5)
self._cells_count_spin.valueChanged.connect(self._rebuild_grid)
cfg_layout.addWidget(self._cells_count_spin)
cfg_layout.addStretch()
root.addLayout(cfg_layout)
# ── Область прокрутки для ячеек ──
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setStyleSheet("QScrollArea { background: transparent; border: none; }")
self._grid_widget = QWidget()
self._grid_layout = QGridLayout(self._grid_widget)
self._grid_layout.setSpacing(8)
self._grid_layout.setContentsMargins(0, 0, 0, 0)
scroll.setWidget(self._grid_widget)
root.addWidget(scroll, 1)
# ── Лог ──
self._log = QLabel("")
self._log.setObjectName("LogLabel")
self._log.setWordWrap(True)
root.addWidget(self._log)
# ── Закрыть ──
btn_layout = QHBoxLayout()
btn_layout.addStretch()
close_btn = QPushButton("Закрыть")
close_btn.setIcon(icon("fa5s.times", color=get_icon_muted()))
close_btn.clicked.connect(self.close)
btn_layout.addWidget(close_btn)
root.addLayout(btn_layout)
# Создаем сетку ячеек при старте
self._rebuild_grid()
def _rebuild_grid(self) -> None:
"""Перестроить сетку ячеек на основе значения spinbox."""
# Очищаем старые элементы из макета
while self._grid_layout.count():
item = self._grid_layout.takeAt(0)
widget = item.widget()
if widget:
widget.deleteLater()
count = self._cells_count_spin.value()
# Заголовки столбцов
headers = ["Ячейка", "Выдать приз (01)", "Открыть ячейку (11)"]
for col, h in enumerate(headers):
lbl = QLabel(f"<b>{h}</b>")
lbl.setAlignment(Qt.AlignmentFlag.AlignCenter)
self._grid_layout.addWidget(lbl, 0, col)
for i in range(count):
row = i + 1
cell_idx = i + 1
lbl_cell = QLabel(f"Ячейка {cell_idx}")
lbl_cell.setAlignment(Qt.AlignmentFlag.AlignCenter)
lbl_cell.setStyleSheet(
"background: #1f2937; border: 1px solid #374151; "
"border-radius: 4px; padding: 4px; color: #f3f4f6;"
)
self._grid_layout.addWidget(lbl_cell, row, 0)
# Выдать приз (2501B)
btn_dispense = QPushButton("Выдать")
btn_dispense.setProperty("role", "primary")
btn_dispense.setIcon(icon("fa5s.gift", color="#ecfdf5"))
btn_dispense.clicked.connect(lambda checked=False, idx=cell_idx: self._dispense_prize(idx))
self._grid_layout.addWidget(btn_dispense, row, 1)
# Открыть ячейку (2511B)
btn_open = QPushButton("Открыть")
btn_open.setIcon(icon("fa5s.unlock", color=get_icon_color()))
btn_open.clicked.connect(lambda checked=False, idx=cell_idx: self._open_cell(idx))
self._grid_layout.addWidget(btn_open, row, 2)
# ── Команды витрины ──
def _query_state(self) -> None:
self._send("25")
def _send_auto_on(self) -> None:
self._send("25991")
def _send_auto_off(self) -> None:
self._send("25990")
def _dispense_prize(self, cell: int) -> None:
self._send(f"2501{cell}")
def _open_cell(self, cell: int) -> None:
self._send(f"2511{cell}")
def _send(self, cmd: str) -> None:
if not cmd:
return
if not self._conn.is_connected:
QMessageBox.warning(self, "Ошибка", "Порт не подключён.")
return
try:
self._conn.send_command(cmd)
self._log.setText(f"→ {cmd}")
resp = self._conn.read_response(timeout=0.4)
self._conn.release()
if resp:
self._log.setText(f"→ {cmd}\n← {resp}")
except Exception as exc:
self._conn.release()
self._log.setText(f"Ошибка: {exc}")

View File

@ -9,6 +9,7 @@ from __future__ import annotations
from PySide6.QtCore import QTimer
from PySide6.QtWidgets import (
QCheckBox,
QColorDialog,
QFormLayout,
QGroupBox,
QHBoxLayout,
@ -112,6 +113,12 @@ class LedTestSection(QWidget):
gamma_layout.addWidget(apply_gamma_btn)
color_form.addRow("Гамма-коррекция:", gamma_layout)
# Пользовательский цвет
self._custom_color_btn = QPushButton("Выбрать и отправить цвет")
self._custom_color_btn.setIcon(icon("fa5s.palette", color=get_icon_color()))
self._custom_color_btn.clicked.connect(self._send_custom_color)
color_form.addRow("Свой цвет (08):", self._custom_color_btn)
# Тест цвета
self._color_test_btn = QPushButton("Тест цвета")
self._color_test_btn.setProperty("role", "primary")
@ -157,6 +164,16 @@ class LedTestSection(QWidget):
cmd = f"89{ch}2{val}"
self._send(cmd)
def _send_custom_color(self) -> None:
color = QColorDialog.getColor(Qt.GlobalColor.white, self, "Выберите цвет")
if color.isValid():
ch = self._cmd89_channel_combo.currentData()
r = color.red()
g = color.green()
b = color.blue()
cmd = f"8{ch}08{r:03d}{g:03d}{b:03d}"
self._send(cmd)
def _run_color_test(self) -> None:
self._color_test_cmds = ["8003", "8004", "8005", "8002", "8000"]
self._color_test_idx = 0

View File

@ -19,6 +19,7 @@ from PySide6.QtWidgets import (
QWidget,
)
from ...services.com_service import ComConnection
from ...ui.icons import get_icon_color, icon
@ -27,10 +28,12 @@ class PaymentSection(QWidget):
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
@ -56,16 +59,32 @@ class PaymentSection(QWidget):
# ── Монетоприёмник ──
coin_group = QGroupBox("Монетоприёмник")
coin_layout = QHBoxLayout(coin_group)
coin_root_layout = QVBoxLayout(coin_group)
coin_row = QHBoxLayout()
self._coin_label = QLabel("Монеты: —")
self._coin_label.setMinimumWidth(120)
coin_layout.addWidget(self._coin_label)
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_layout.addWidget(self._coin_confirm_btn)
coin_layout.addStretch()
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)
# ── Хопперы ──
@ -133,6 +152,7 @@ class PaymentSection(QWidget):
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))
@ -215,3 +235,20 @@ class PaymentSection(QWidget):
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}")