plate-config-app/app/widgets/dialogs/output_test_dialog.py
Danil Iarosh 448b9bb07a 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).
2026-06-30 09:22:36 +03:00

258 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Диалог «Тест силовых выходов» — управление 8 силовыми выходами платы.
Команда 21XY:
X — индекс выхода (0-7)
Y — действие: 0=выключить, 1=включить, 2=инвертировать
"""
from __future__ import annotations
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QFormLayout,
QGridLayout,
QGroupBox,
QHBoxLayout,
QLabel,
QMessageBox,
QPushButton,
QVBoxLayout,
)
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
class OutputTestDialog(StyledDialog):
"""Диалог для тестирования силовых выходов платы (cmd 21XY)."""
def __init__(self, connection: ComConnection, *, parent=None) -> None:
super().__init__(parent)
self._conn = connection
self.setWindowTitle("Тест силовых выходов")
self.setMinimumWidth(500)
self._build_ui()
# ── UI ──────────────────────────────────────────────────────────────
def _build_ui(self) -> None:
root = self.containerLayout()
root.setSpacing(12)
pal = get_palette(settings_service.theme_mode(), settings_service.theme_accent())
# ── Описание ──
desc = QLabel(
"Управление силовыми выходами платы (команда 21XY).\n"
"X — индекс выхода (0–7), Y — действие: 1=вкл, 0=выкл, 2=инверсия."
)
desc.setWordWrap(True)
desc.setStyleSheet(f"color: {pal.text_muted}; font-size: 12px; margin-bottom: 4px;")
root.addWidget(desc)
# ── Сетка выходов ──
outputs_group = QGroupBox("Силовые выходы")
grid = QGridLayout(outputs_group)
grid.setSpacing(6)
# Заголовки столбцов
headers = ["Выход", "Включить", "Выключить", "Инвертировать"]
for col, header in enumerate(headers):
lbl = QLabel(f"<b>{header}</b>")
lbl.setAlignment(Qt.AlignmentFlag.AlignCenter)
grid.addWidget(lbl, 0, col)
self._state_labels: list[QLabel] = []
for i in range(_NUM_OUTPUTS):
row = i + 1
# Номер выхода
num_label = QLabel(f" Выход {i + 1} ")
num_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
num_label.setStyleSheet(
f"background: {pal.btn_bg}; border: 1px solid {pal.border}; "
f"border-radius: 6px; padding: 4px 8px; font-weight: 600;"
f" color: {pal.text_primary};"
)
grid.addWidget(num_label, row, 0)
# Включить
on_btn = QPushButton("Вкл")
on_btn.setProperty("role", "primary")
on_btn.setIcon(icon("fa5s.toggle-on", color="#ecfdf5"))
on_btn.setMinimumHeight(30)
on_btn.clicked.connect(lambda checked=False, idx=i: self._set_output(idx, 1))
grid.addWidget(on_btn, row, 1)
# Выключить
off_btn = QPushButton("Выкл")
off_btn.setIcon(icon("fa5s.toggle-off", color=get_icon_color()))
off_btn.setMinimumHeight(30)
off_btn.clicked.connect(lambda checked=False, idx=i: self._set_output(idx, 0))
grid.addWidget(off_btn, row, 2)
# Инвертировать
inv_btn = QPushButton("Инверт")
inv_btn.setIcon(icon("fa5s.exchange-alt", color=get_icon_color()))
inv_btn.setMinimumHeight(30)
inv_btn.clicked.connect(lambda checked=False, idx=i: self._set_output(idx, 2))
grid.addWidget(inv_btn, row, 3)
root.addWidget(outputs_group)
# ── Общие действия ──
bulk_layout = QHBoxLayout()
bulk_layout.addStretch()
all_on_btn = QPushButton("Включить все")
all_on_btn.setProperty("role", "primary")
all_on_btn.setIcon(icon("fa5s.bolt", color="#ecfdf5"))
all_on_btn.clicked.connect(lambda: self._set_all(1))
bulk_layout.addWidget(all_on_btn)
all_off_btn = QPushButton("Выключить все")
all_off_btn.setIcon(icon("fa5s.power-off", color=get_icon_color()))
all_off_btn.clicked.connect(lambda: self._set_all(0))
bulk_layout.addWidget(all_off_btn)
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")
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)
# ── Логика ──────────────────────────────────────────────────────────
def _set_output(self, index: int, action: int) -> None:
"""Отправить команду 21XY для одного выхода."""
cmd = f"21{index}{action}"
self._send(cmd)
def _set_all(self, action: int) -> None:
"""Включить/выключить все 8 выходов через команду 20X.
Команда 20X задаёт состояние всех выходов двоичной маской X.
Все включить: 20255 (0xFF), все выключить: 200.
"""
if action == 1:
cmd = "20255"
else:
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
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.3)
self._conn.release()
if resp:
self._log.setText(f"→ {cmd}\n← {resp}")
except Exception as exc:
self._conn.release()
self._log.setText(f"Ошибка: {exc}")