diff --git a/app/widgets/connection_tab.py b/app/widgets/connection_tab.py index 029ccfe..c355eec 100644 --- a/app/widgets/connection_tab.py +++ b/app/widgets/connection_tab.py @@ -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( diff --git a/app/widgets/dialogs/led_test_dialog.py b/app/widgets/dialogs/led_test_dialog.py index a8c5016..09ffbd0 100644 --- a/app/widgets/dialogs/led_test_dialog.py +++ b/app/widgets/dialogs/led_test_dialog.py @@ -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("Direct-шкалы (только для Морского боя s_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 diff --git a/app/widgets/dialogs/output_test_dialog.py b/app/widgets/dialogs/output_test_dialog.py index 5c87f9f..4094e2a 100644 --- a/app/widgets/dialogs/output_test_dialog.py +++ b/app/widgets/dialogs/output_test_dialog.py @@ -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 diff --git a/app/widgets/dialogs/prize_showcase_dialog.py b/app/widgets/dialogs/prize_showcase_dialog.py new file mode 100644 index 0000000..3198c33 --- /dev/null +++ b/app/widgets/dialogs/prize_showcase_dialog.py @@ -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"{h}") + 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}") diff --git a/app/widgets/sections/led_test_section.py b/app/widgets/sections/led_test_section.py index 21d8fdb..dcd1b81 100644 --- a/app/widgets/sections/led_test_section.py +++ b/app/widgets/sections/led_test_section.py @@ -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 diff --git a/app/widgets/sections/payment_section.py b/app/widgets/sections/payment_section.py index 8b1782f..515c09d 100644 --- a/app/widgets/sections/payment_section.py +++ b/app/widgets/sections/payment_section.py @@ -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}")