- Переименованы элементы выбора физического канала в настройках WS2812: "1-й канал" -> "2-й канал", "2-й" -> "3-й", "3-й" -> "4-й". - Удален неиспользуемый "4-й канал (4)", так как команды 894XY не существует. - Изменения внесены в LedTestDialog и LedTestSection. - Версия приложения обновлена до 1.4.1.
631 lines
25 KiB
Python
631 lines
25 KiB
Python
"""Диалог «Тест подсветки» — управление адресными диодами платы.
|
||
|
||
Загружает конфигурации эффектов из .lm файлов в папке light_modes/.
|
||
Поддерживает два формата команд:
|
||
- Простой «8XYY»: канал X (0=все, 1-4), программа YY
|
||
- С длительностью «85XXYY»: XX — эффект, YY — время в секундах
|
||
- Цветовое чередование «89[X]» (0=RGB … 5=GBR)
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
|
||
from PySide6.QtCore import Qt, QTimer
|
||
from PySide6.QtWidgets import (
|
||
QCheckBox,
|
||
QColorDialog,
|
||
QFormLayout,
|
||
QGroupBox,
|
||
QHBoxLayout,
|
||
QLabel,
|
||
QMessageBox,
|
||
QPushButton,
|
||
QSpinBox,
|
||
QVBoxLayout,
|
||
QWidget,
|
||
)
|
||
|
||
from ...services.com_service import ComConnection
|
||
from ...services.light_modes_service import (
|
||
LightMode,
|
||
get_light_modes_dir,
|
||
load_all_light_modes,
|
||
)
|
||
from ...ui.icons import get_icon_color, get_icon_muted, icon
|
||
from ..no_wheel_combo_box import NoWheelComboBox
|
||
from .styled_dialog import StyledDialog
|
||
|
||
_COLOR_ORDERS: list[tuple[int, str]] = [
|
||
(0, "RGB"),
|
||
(1, "RBG"),
|
||
(2, "BRG"),
|
||
(3, "BGR"),
|
||
(4, "GRB"),
|
||
(5, "GBR"),
|
||
]
|
||
|
||
|
||
class LedTestDialog(StyledDialog):
|
||
def __init__(self, connection: ComConnection, parent=None) -> None:
|
||
super().__init__(parent)
|
||
self._conn = connection
|
||
self._modes: list[LightMode] = []
|
||
self.setWindowTitle("Тест подсветки")
|
||
self.setMinimumWidth(480)
|
||
self._load_modes()
|
||
self._setup_ui()
|
||
|
||
def _load_modes(self) -> None:
|
||
self._modes = load_all_light_modes()
|
||
|
||
def _setup_ui(self) -> None:
|
||
root = self.containerLayout()
|
||
root.setSpacing(12)
|
||
|
||
# ── Выбор проекта (игры) ──
|
||
project_group = QGroupBox("Игровой проект")
|
||
project_layout = QVBoxLayout(project_group)
|
||
|
||
proj_row = QHBoxLayout()
|
||
self._project_combo = NoWheelComboBox()
|
||
for mode in self._modes:
|
||
self._project_combo.addItem(mode.project_name, mode)
|
||
self._project_combo.currentIndexChanged.connect(self._on_project_changed)
|
||
proj_row.addWidget(self._project_combo, 1)
|
||
|
||
reload_btn = QPushButton()
|
||
reload_btn.setIcon(icon("fa5s.sync-alt", color=get_icon_color()))
|
||
reload_btn.setToolTip("Перечитать файлы конфигураций")
|
||
reload_btn.setFixedWidth(36)
|
||
reload_btn.clicked.connect(self._reload_modes)
|
||
proj_row.addWidget(reload_btn)
|
||
|
||
open_dir_btn = QPushButton()
|
||
open_dir_btn.setIcon(icon("fa5s.folder-open", color=get_icon_color()))
|
||
open_dir_btn.setToolTip("Открыть папку light_modes")
|
||
open_dir_btn.setFixedWidth(36)
|
||
open_dir_btn.clicked.connect(self._open_modes_dir)
|
||
proj_row.addWidget(open_dir_btn)
|
||
|
||
project_layout.addLayout(proj_row)
|
||
root.addWidget(project_group)
|
||
|
||
# ── Эффекты ──
|
||
effect_group = QGroupBox("Эффекты")
|
||
effect_form = QFormLayout(effect_group)
|
||
|
||
# Канал (только для base_command = "8")
|
||
self._channel_combo = NoWheelComboBox()
|
||
self._channel_combo.addItem("Все каналы (0)", 0)
|
||
for ch in range(1, 5):
|
||
self._channel_combo.addItem(f"Канал {ch}", ch)
|
||
self._channel_label = QLabel("Канал:")
|
||
effect_form.addRow(self._channel_label, self._channel_combo)
|
||
|
||
# Список эффектов из конфига
|
||
self._effect_combo = NoWheelComboBox()
|
||
effect_form.addRow("Эффект:", self._effect_combo)
|
||
|
||
# Длительность (только для has_duration = true)
|
||
self._duration_spin = QSpinBox()
|
||
self._duration_spin.setRange(1, 99)
|
||
self._duration_spin.setValue(5)
|
||
self._duration_spin.setSuffix(" сек")
|
||
self._duration_label = QLabel("Длительность:")
|
||
effect_form.addRow(self._duration_label, self._duration_spin)
|
||
|
||
# Ручной код эффекта
|
||
self._custom_code_spin = QSpinBox()
|
||
self._custom_code_spin.setRange(0, 99)
|
||
self._custom_code_spin.setValue(10)
|
||
effect_form.addRow("Ручной код эффекта:", self._custom_code_spin)
|
||
|
||
# Кнопки отправки
|
||
btn_row = QHBoxLayout()
|
||
send_effect_btn = QPushButton("Отправить эффект")
|
||
send_effect_btn.setProperty("role", "primary")
|
||
send_effect_btn.setIcon(icon("fa5s.paper-plane", color="#ecfdf5"))
|
||
send_effect_btn.clicked.connect(self._send_effect)
|
||
btn_row.addWidget(send_effect_btn)
|
||
|
||
send_custom_btn = QPushButton("Ручной код")
|
||
send_custom_btn.setIcon(icon("fa5s.paper-plane", color=get_icon_muted()))
|
||
send_custom_btn.clicked.connect(self._send_custom_effect)
|
||
btn_row.addWidget(send_custom_btn)
|
||
|
||
# Кнопка «выключить» (base_command без параметра)
|
||
self._stop_btn = QPushButton("Выключить эффекты")
|
||
self._stop_btn.setIcon(icon("fa5s.stop", color=get_icon_muted()))
|
||
self._stop_btn.clicked.connect(self._send_stop)
|
||
btn_row.addWidget(self._stop_btn)
|
||
|
||
effect_form.addRow(btn_row)
|
||
root.addWidget(effect_group)
|
||
|
||
# ── Настройки WS2812 (cmd 89) ──
|
||
color_group = QGroupBox("Настройки WS2812 (cmd 89)")
|
||
color_form = QFormLayout(color_group)
|
||
color_form.setSpacing(6)
|
||
|
||
# Выбор физического канала
|
||
self._cmd89_channel_combo = NoWheelComboBox()
|
||
self._cmd89_channel_combo.addItem("Все ленты (0)", 0)
|
||
self._cmd89_channel_combo.addItem("2-й канал (1)", 1)
|
||
self._cmd89_channel_combo.addItem("3-й канал (2)", 2)
|
||
self._cmd89_channel_combo.addItem("4-й канал (3)", 3)
|
||
color_form.addRow("Физический канал:", self._cmd89_channel_combo)
|
||
|
||
# Порядок цветов
|
||
self._color_order_combo = NoWheelComboBox()
|
||
for code, name in _COLOR_ORDERS:
|
||
self._color_order_combo.addItem(f"{code} — {name}", code)
|
||
|
||
apply_order_btn = QPushButton("Применить порядок")
|
||
apply_order_btn.setProperty("role", "primary")
|
||
apply_order_btn.setIcon(icon("fa5s.palette", color="#ecfdf5"))
|
||
apply_order_btn.clicked.connect(self._send_color_order)
|
||
|
||
order_layout = QHBoxLayout()
|
||
order_layout.addWidget(self._color_order_combo, 1)
|
||
order_layout.addWidget(apply_order_btn)
|
||
color_form.addRow("Порядок цветов:", order_layout)
|
||
|
||
# Яркость
|
||
self._cmd89_brightness_spin = QSpinBox()
|
||
self._cmd89_brightness_spin.setRange(0, 255)
|
||
self._cmd89_brightness_spin.setValue(255)
|
||
|
||
apply_bright_btn = QPushButton("Применить яркость")
|
||
apply_bright_btn.setIcon(icon("fa5s.sun", color=get_icon_color()))
|
||
apply_bright_btn.clicked.connect(self._send_brightness)
|
||
|
||
bright_layout = QHBoxLayout()
|
||
bright_layout.addWidget(self._cmd89_brightness_spin, 1)
|
||
bright_layout.addWidget(apply_bright_btn)
|
||
color_form.addRow("Яркость:", bright_layout)
|
||
|
||
# Гамма-коррекция
|
||
self._cmd89_gamma_check = QCheckBox("Включить гамма-коррекцию")
|
||
|
||
apply_gamma_btn = QPushButton("Применить гамму")
|
||
apply_gamma_btn.setIcon(icon("fa5s.adjust", color=get_icon_color()))
|
||
apply_gamma_btn.clicked.connect(self._send_gamma)
|
||
|
||
gamma_layout = QHBoxLayout()
|
||
gamma_layout.addWidget(self._cmd89_gamma_check, 1)
|
||
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")
|
||
self._color_test_btn.setIcon(icon("fa5s.eye", color="#ecfdf5"))
|
||
self._color_test_btn.clicked.connect(self._run_color_test)
|
||
color_form.addRow(self._color_test_btn)
|
||
|
||
color_hint = QLabel("Должны вывестись: красный → зелёный → синий")
|
||
color_hint.setStyleSheet("color: #9ca3af; font-size: 11px;")
|
||
color_hint.setWordWrap(True)
|
||
color_form.addRow(color_hint)
|
||
|
||
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_strip_label = QLabel("Лента:")
|
||
self._m_strip_label.setVisible(False)
|
||
test_type_layout.addWidget(self._m_strip_label)
|
||
|
||
self._m_strip_spin = QSpinBox()
|
||
self._m_strip_spin.setRange(1, 4)
|
||
self._m_strip_spin.setValue(1)
|
||
self._m_strip_spin.setVisible(False)
|
||
test_type_layout.addWidget(self._m_strip_spin)
|
||
|
||
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")
|
||
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._on_project_changed()
|
||
|
||
# ── Переключение проектов ──
|
||
|
||
def _current_mode(self) -> LightMode | None:
|
||
return self._project_combo.currentData()
|
||
|
||
def _on_project_changed(self) -> None:
|
||
"""Обновить список эффектов и видимость элементов при смене проекта."""
|
||
mode = self._current_mode()
|
||
self._effect_combo.clear()
|
||
if mode is None:
|
||
return
|
||
|
||
for eff in mode.effects:
|
||
self._effect_combo.addItem(f"{eff.code:02d} — {eff.name}", eff.code)
|
||
|
||
# Канал — только для простых команд (base_command = "8")
|
||
is_channel_mode = mode.base_command == "8"
|
||
self._channel_combo.setVisible(is_channel_mode)
|
||
self._channel_label.setVisible(is_channel_mode)
|
||
|
||
# Длительность — только для has_duration
|
||
self._duration_spin.setVisible(mode.has_duration)
|
||
self._duration_label.setVisible(mode.has_duration)
|
||
|
||
# Кнопка «выключить» — только для 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()
|
||
self._load_modes()
|
||
self._project_combo.clear()
|
||
for mode in self._modes:
|
||
self._project_combo.addItem(mode.project_name, mode)
|
||
# Попробуем восстановить выбор
|
||
idx = self._project_combo.findText(current_name)
|
||
if idx >= 0:
|
||
self._project_combo.setCurrentIndex(idx)
|
||
self._log.setText(f"Загружено {len(self._modes)} конфигураций")
|
||
|
||
def _open_modes_dir(self) -> None:
|
||
"""Открыть папку light_modes/ в проводнике."""
|
||
modes_dir = get_light_modes_dir()
|
||
if not modes_dir.exists():
|
||
modes_dir.mkdir(parents=True, exist_ok=True)
|
||
if sys.platform == "win32":
|
||
os.startfile(str(modes_dir)) # noqa: S606
|
||
elif sys.platform == "darwin":
|
||
subprocess.Popen(["open", str(modes_dir)]) # noqa: S603, S607
|
||
else:
|
||
subprocess.Popen(["xdg-open", str(modes_dir)]) # noqa: S603, S607
|
||
|
||
# ── Построение команды ──
|
||
|
||
def _build_command(self, effect_code: int) -> str:
|
||
"""Собрать команду на основе текущего проекта."""
|
||
mode = self._current_mode()
|
||
if mode is None:
|
||
return ""
|
||
|
||
if mode.has_duration:
|
||
# Формат: base_command + XX (код) + YY (время)
|
||
duration = self._duration_spin.value()
|
||
return f"{mode.base_command}{effect_code:02d}{duration:02d}"
|
||
else:
|
||
# Формат: base_command + X (канал) + YY (код)
|
||
channel = self._channel_combo.currentData() or 0
|
||
return f"{mode.base_command}{channel}{effect_code:02d}"
|
||
|
||
# ── Отправка ──
|
||
|
||
def _send_effect(self) -> None:
|
||
code = self._effect_combo.currentData()
|
||
if code is None:
|
||
return
|
||
cmd = self._build_command(code)
|
||
self._send(cmd)
|
||
|
||
def _send_custom_effect(self) -> None:
|
||
code = self._custom_code_spin.value()
|
||
cmd = self._build_command(code)
|
||
self._send(cmd)
|
||
|
||
def _send_stop(self) -> None:
|
||
"""Отправить base_command без параметра (отключить эффекты)."""
|
||
mode = self._current_mode()
|
||
if mode is None:
|
||
return
|
||
self._send(mode.base_command)
|
||
|
||
def _send_color_order(self) -> None:
|
||
ch = self._cmd89_channel_combo.currentData()
|
||
code = self._color_order_combo.currentData()
|
||
cmd = f"89{ch}0{code}"
|
||
self._send(cmd)
|
||
|
||
def _send_brightness(self) -> None:
|
||
ch = self._cmd89_channel_combo.currentData()
|
||
val = self._cmd89_brightness_spin.value()
|
||
cmd = f"89{ch}1{val}"
|
||
self._send(cmd)
|
||
|
||
def _send_gamma(self) -> None:
|
||
ch = self._cmd89_channel_combo.currentData()
|
||
val = 1 if self._cmd89_gamma_check.isChecked() else 0
|
||
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"]
|
||
self._color_test_idx = 0
|
||
self._color_test_btn.setEnabled(False)
|
||
|
||
self._color_test_timer = QTimer(self)
|
||
self._color_test_timer.setInterval(1000)
|
||
self._color_test_timer.timeout.connect(self._color_test_step)
|
||
self._color_test_step() # отправить первую команду сразу
|
||
self._color_test_timer.start()
|
||
|
||
def _color_test_step(self) -> None:
|
||
if self._color_test_idx >= len(self._color_test_cmds):
|
||
self._color_test_timer.stop()
|
||
self._color_test_btn.setEnabled(True)
|
||
return
|
||
cmd = self._color_test_cmds[self._color_test_idx]
|
||
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)
|
||
is_pixel_test = (test_type == 4)
|
||
self._m_strip_label.setVisible(is_pixel_test)
|
||
self._m_strip_spin.setVisible(is_pixel_test)
|
||
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:
|
||
strip = self._m_strip_spin.value()
|
||
pixel = self._m_test_param.value()
|
||
cmd = f"{mode.base_command}254{strip}{pixel:02d}"
|
||
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
|
||
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}")
|