- Добавлена поддержка выбора произвольного цвета (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).
258 lines
10 KiB
Python
258 lines
10 KiB
Python
"""Диалог «Тест силовых выходов» — управление 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}")
|