564 lines
24 KiB
Python
564 lines
24 KiB
Python
"""Small UI builders used by the main laser-control window."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from PyQt6.QtWidgets import (
|
||
QCheckBox,
|
||
QComboBox,
|
||
QDoubleSpinBox,
|
||
QFormLayout,
|
||
QGridLayout,
|
||
QGroupBox,
|
||
QHBoxLayout,
|
||
QLabel,
|
||
QPlainTextEdit,
|
||
QPushButton,
|
||
QSizePolicy,
|
||
QSpinBox,
|
||
QTabWidget,
|
||
QTextEdit,
|
||
QVBoxLayout,
|
||
QWidget,
|
||
)
|
||
|
||
from laser_control.constants import (
|
||
AD9102_PAT_BASE_MAX,
|
||
AD9102_PAT_BASE_MIN,
|
||
AD9102_PAT_PERIOD_MAX,
|
||
AD9102_PAT_PERIOD_MIN,
|
||
AD9102_SAW_STEP_MAX,
|
||
AD9102_SAW_STEP_MIN,
|
||
AD9102_SRAM_AMPLITUDE_MAX,
|
||
AD9102_SRAM_AMPLITUDE_MIN,
|
||
AD9102_SRAM_HOLD_MAX,
|
||
AD9102_SRAM_HOLD_MIN,
|
||
AD9102_SRAM_SAMPLE_MAX,
|
||
AD9102_SRAM_SAMPLE_MIN,
|
||
CURRENT_MAX_MA,
|
||
CURRENT_MIN_MA,
|
||
DEFAULT_AD9102_AMPLITUDE,
|
||
DEFAULT_AD9102_SAW_FREQUENCY_HZ,
|
||
DEFAULT_AD9102_SRAM_FREQUENCY_HZ,
|
||
DEFAULT_AD9102_HOLD_CYCLES,
|
||
DEFAULT_AD9102_PAT_BASE,
|
||
DEFAULT_AD9102_PAT_PERIOD,
|
||
DEFAULT_AD9102_SAMPLE_COUNT,
|
||
DEFAULT_AD9102_SAW_STEP,
|
||
DEFAULT_AD9833_FREQUENCY_HZ,
|
||
DEFAULT_CURRENT1_MA,
|
||
DEFAULT_CURRENT2_MA,
|
||
DEFAULT_DS1809_COUNT,
|
||
DEFAULT_DS1809_PROFILE_POSITION,
|
||
DEFAULT_DS1809_PULSE_MS,
|
||
DEFAULT_STM32_DAC_CODE,
|
||
DEFAULT_TEMP1_C,
|
||
DEFAULT_TEMP2_C,
|
||
DS1809_COUNT_MAX,
|
||
DS1809_COUNT_MIN,
|
||
DS1809_PROFILE_POSITION_MAX,
|
||
DS1809_PROFILE_POSITION_MIN,
|
||
DS1809_PULSE_MS_MAX,
|
||
DS1809_PULSE_MS_MIN,
|
||
AD9833_MCLK_HZ,
|
||
AD9833_OUTPUT_FREQ_MAX_HZ,
|
||
AD9833_OUTPUT_FREQ_MIN_HZ,
|
||
STM32_DAC_CODE_MAX,
|
||
STM32_DAC_CODE_MIN,
|
||
TEMP_MAX_C,
|
||
TEMP_MIN_C,
|
||
)
|
||
|
||
|
||
def _double_spinbox(
|
||
minimum: float,
|
||
maximum: float,
|
||
value: float,
|
||
*,
|
||
decimals: int = 2,
|
||
step: float = 0.1,
|
||
suffix: str = "",
|
||
) -> QDoubleSpinBox:
|
||
box = QDoubleSpinBox()
|
||
box.setRange(minimum, maximum)
|
||
box.setDecimals(decimals)
|
||
box.setSingleStep(step)
|
||
box.setValue(value)
|
||
if suffix:
|
||
box.setSuffix(suffix)
|
||
return box
|
||
|
||
|
||
def _int_spinbox(minimum: int, maximum: int, value: int, *, suffix: str = "") -> QSpinBox:
|
||
box = QSpinBox()
|
||
box.setRange(minimum, maximum)
|
||
box.setValue(value)
|
||
if suffix:
|
||
box.setSuffix(suffix)
|
||
return box
|
||
|
||
|
||
def _expanding_button(label: str, *, primary: bool = False) -> QPushButton:
|
||
button = QPushButton(label)
|
||
button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||
if primary:
|
||
button.setObjectName("primaryButton")
|
||
return button
|
||
|
||
|
||
def build_manual_group(owner) -> QGroupBox:
|
||
"""Create manual control inputs."""
|
||
group = QGroupBox("Ручной режим")
|
||
layout = QFormLayout(group)
|
||
layout.setHorizontalSpacing(12)
|
||
layout.setVerticalSpacing(8)
|
||
|
||
owner._manual_temp1 = _double_spinbox(TEMP_MIN_C, TEMP_MAX_C, DEFAULT_TEMP1_C, suffix=" °C")
|
||
owner._manual_temp2 = _double_spinbox(TEMP_MIN_C, TEMP_MAX_C, DEFAULT_TEMP2_C, suffix=" °C")
|
||
owner._manual_current1 = _double_spinbox(
|
||
CURRENT_MIN_MA,
|
||
CURRENT_MAX_MA,
|
||
DEFAULT_CURRENT1_MA,
|
||
decimals=3,
|
||
step=0.05,
|
||
suffix=" мА",
|
||
)
|
||
owner._manual_current2 = _double_spinbox(
|
||
CURRENT_MIN_MA,
|
||
CURRENT_MAX_MA,
|
||
DEFAULT_CURRENT2_MA,
|
||
decimals=3,
|
||
step=0.05,
|
||
suffix=" мА",
|
||
)
|
||
|
||
layout.addRow("Температура лазера 1", owner._manual_temp1)
|
||
layout.addRow("Температура лазера 2", owner._manual_temp2)
|
||
layout.addRow("Ток лазера 1", owner._manual_current1)
|
||
layout.addRow("Ток лазера 2", owner._manual_current2)
|
||
|
||
owner._apply_manual_button = _expanding_button("Применить", primary=True)
|
||
owner._apply_manual_button.clicked.connect(owner._on_apply_manual)
|
||
layout.addRow(owner._apply_manual_button)
|
||
return group
|
||
|
||
|
||
def build_device_group(owner) -> QGroupBox:
|
||
"""Create compact tabs for supported peripheral commands."""
|
||
group = QGroupBox("Периферия")
|
||
layout = QVBoxLayout(group)
|
||
|
||
tabs = QTabWidget(group)
|
||
tabs.addTab(_build_ad9102_tab(owner), "Генератор AD9102")
|
||
tabs.addTab(_build_ad9833_tab(owner), "Генератор AD9833")
|
||
tabs.addTab(_build_aux_tab(owner), "Выходы и DS1809")
|
||
tabs.addTab(_build_wave_tab(owner), "Своя форма")
|
||
layout.addWidget(tabs)
|
||
return group
|
||
|
||
|
||
def _build_ad9102_tab(owner) -> QWidget:
|
||
tab = QWidget()
|
||
layout = QVBoxLayout(tab)
|
||
layout.setSpacing(10)
|
||
|
||
note = QLabel(
|
||
"AD9102 в этой прошивке умеет два режима. "
|
||
"Первый: встроенная пила или треугольник самого AD9102. "
|
||
"Второй: STM32 сама строит пилу или треугольник из точек и записывает их в память AD9102. "
|
||
"Для упрощённого режима ниже задаются понятные величины, а регистровые поля спрятаны в расширенных настройках."
|
||
)
|
||
note.setWordWrap(True)
|
||
note.setObjectName("captionLabel")
|
||
|
||
basic_group = QGroupBox("Основные настройки")
|
||
basic_layout = QFormLayout(basic_group)
|
||
basic_layout.setHorizontalSpacing(12)
|
||
basic_layout.setVerticalSpacing(8)
|
||
|
||
owner._ad9102_enable = QCheckBox("Подать сигнал на выход")
|
||
owner._ad9102_enable.setChecked(True)
|
||
owner._ad9102_mode = QComboBox()
|
||
owner._ad9102_mode.addItem("Встроенная пила/треугольник AD9102", "saw")
|
||
owner._ad9102_mode.addItem("Пила/треугольник через память AD9102", "sram")
|
||
owner._ad9102_shape = QComboBox()
|
||
owner._ad9102_shape.addItem("Пила", "saw")
|
||
owner._ad9102_shape.addItem("Треугольник", "triangle")
|
||
owner._ad9102_shape.setCurrentIndex(1)
|
||
owner._ad9102_frequency_hz = _int_spinbox(
|
||
1,
|
||
DEFAULT_AD9102_SRAM_FREQUENCY_HZ,
|
||
DEFAULT_AD9102_SAW_FREQUENCY_HZ,
|
||
suffix=" Гц",
|
||
)
|
||
owner._ad9102_frequency_hz.setSingleStep(100)
|
||
owner._ad9102_frequency_hz.setGroupSeparatorShown(True)
|
||
owner._ad9102_basic_hint = QLabel()
|
||
owner._ad9102_basic_hint.setWordWrap(True)
|
||
owner._ad9102_basic_hint.setObjectName("captionLabel")
|
||
owner._ad9102_preview = QLabel("Реальная частота: —")
|
||
owner._ad9102_preview.setObjectName("valueLabel")
|
||
owner._ad9102_advanced_toggle = QCheckBox("Показать расширенные параметры AD9102")
|
||
|
||
owner._ad9102_saw_step = _int_spinbox(
|
||
AD9102_SAW_STEP_MIN,
|
||
AD9102_SAW_STEP_MAX,
|
||
DEFAULT_AD9102_SAW_STEP,
|
||
)
|
||
owner._ad9102_pat_base = _int_spinbox(
|
||
AD9102_PAT_BASE_MIN,
|
||
AD9102_PAT_BASE_MAX,
|
||
DEFAULT_AD9102_PAT_BASE,
|
||
)
|
||
owner._ad9102_pat_period = _int_spinbox(
|
||
AD9102_PAT_PERIOD_MIN,
|
||
AD9102_PAT_PERIOD_MAX,
|
||
DEFAULT_AD9102_PAT_PERIOD,
|
||
)
|
||
owner._ad9102_sample_count = _int_spinbox(
|
||
AD9102_SRAM_SAMPLE_MIN,
|
||
AD9102_SRAM_SAMPLE_MAX,
|
||
DEFAULT_AD9102_SAMPLE_COUNT,
|
||
)
|
||
owner._ad9102_hold_cycles = _int_spinbox(
|
||
AD9102_SRAM_HOLD_MIN,
|
||
AD9102_SRAM_HOLD_MAX,
|
||
DEFAULT_AD9102_HOLD_CYCLES,
|
||
)
|
||
owner._ad9102_amplitude = _int_spinbox(
|
||
AD9102_SRAM_AMPLITUDE_MIN,
|
||
AD9102_SRAM_AMPLITUDE_MAX,
|
||
DEFAULT_AD9102_AMPLITUDE,
|
||
)
|
||
owner._ad9102_use_amplitude = QCheckBox(
|
||
"Использовать формат \"размах + число точек\" вместо \"пауза + число точек\""
|
||
)
|
||
owner._ad9102_mode.currentIndexChanged.connect(owner._on_ad9102_mode_changed)
|
||
owner._ad9102_shape.currentIndexChanged.connect(owner._update_ad9102_form)
|
||
owner._ad9102_frequency_hz.valueChanged.connect(owner._update_ad9102_form)
|
||
owner._ad9102_advanced_toggle.toggled.connect(owner._update_ad9102_form)
|
||
owner._ad9102_use_amplitude.toggled.connect(owner._update_ad9102_form)
|
||
owner._ad9102_sample_count.valueChanged.connect(owner._update_ad9102_form)
|
||
owner._ad9102_hold_cycles.valueChanged.connect(owner._update_ad9102_form)
|
||
owner._ad9102_saw_step.valueChanged.connect(owner._update_ad9102_form)
|
||
owner._ad9102_amplitude.valueChanged.connect(owner._update_ad9102_form)
|
||
owner._ad9102_use_amplitude.setChecked(True)
|
||
|
||
basic_layout.addRow(owner._ad9102_enable)
|
||
basic_layout.addRow("Режим генерации", owner._ad9102_mode)
|
||
basic_layout.addRow("Форма сигнала", owner._ad9102_shape)
|
||
basic_layout.addRow("Частота сигнала", owner._ad9102_frequency_hz)
|
||
basic_layout.addRow("Размах сигнала в режиме памяти (0..8191)", owner._ad9102_amplitude)
|
||
basic_layout.addRow(owner._ad9102_preview)
|
||
basic_layout.addRow(owner._ad9102_basic_hint)
|
||
|
||
owner._ad9102_advanced_group = QGroupBox("Расширенные параметры")
|
||
advanced_layout = QFormLayout(owner._ad9102_advanced_group)
|
||
advanced_layout.setHorizontalSpacing(12)
|
||
advanced_layout.setVerticalSpacing(8)
|
||
advanced_layout.addRow("Скорость нарастания пилы (1..63)", owner._ad9102_saw_step)
|
||
advanced_layout.addRow("Масштаб периода (0..15)", owner._ad9102_pat_base)
|
||
advanced_layout.addRow("Длина периода (0..65535)", owner._ad9102_pat_period)
|
||
advanced_layout.addRow("Число точек формы (2..4096)", owner._ad9102_sample_count)
|
||
advanced_layout.addRow("Пауза на каждой точке (0..15)", owner._ad9102_hold_cycles)
|
||
advanced_layout.addRow(owner._ad9102_use_amplitude)
|
||
|
||
layout.addWidget(note)
|
||
layout.addWidget(basic_group)
|
||
layout.addWidget(owner._ad9102_advanced_toggle)
|
||
layout.addWidget(owner._ad9102_advanced_group)
|
||
|
||
owner._apply_ad9102_button = _expanding_button("Применить настройки генератора", primary=True)
|
||
owner._apply_ad9102_button.clicked.connect(owner._on_apply_ad9102)
|
||
layout.addWidget(owner._apply_ad9102_button)
|
||
layout.addStretch(1)
|
||
|
||
owner._ad9102_saw_step.setToolTip(
|
||
"Код шага нарастания пилообразного сигнала. Диапазон 1..63. "
|
||
"Чем больше значение, тем быстрее растёт сигнал внутри одного периода."
|
||
)
|
||
owner._ad9102_pat_base.setToolTip(
|
||
"Грубый масштаб периода повторения. Диапазон 0..15. "
|
||
"Используется вместе с длиной периода и задаёт базу времени генератора."
|
||
)
|
||
owner._ad9102_pat_period.setToolTip(
|
||
"Точная длина периода повторения. Диапазон 0..65535. "
|
||
"Совместно с масштабом периода определяет, как часто форма начинается заново."
|
||
)
|
||
owner._ad9102_sample_count.setToolTip(
|
||
"Количество отсчётов формы в памяти SRAM. Диапазон 2..4096. "
|
||
"Один период в режиме памяти состоит из этого числа точек."
|
||
)
|
||
owner._ad9102_hold_cycles.setToolTip(
|
||
"Количество внутренних циклов удержания одной точки формы. Диапазон 0..15. "
|
||
"Чем больше значение, тем дольше каждая точка удерживается перед переходом к следующей."
|
||
)
|
||
owner._ad9102_amplitude.setToolTip(
|
||
"Амплитудный коэффициент для режима, где STM32 сама строит пилу или треугольник "
|
||
"и записывает их в память AD9102. Диапазон 0..8191. Чем больше значение, тем больше размах сигнала."
|
||
)
|
||
owner._ad9102_frequency_hz.setToolTip(
|
||
"Желаемая частота сигнала в герцах. "
|
||
"Интерфейс автоматически подберёт ближайшие поддерживаемые параметры AD9102."
|
||
)
|
||
owner._ad9102_preview.setToolTip(
|
||
"Показывает, какая реальная частота получится после округления к поддерживаемым параметрам чипа."
|
||
)
|
||
owner._ad9102_use_amplitude.setToolTip(
|
||
"Ограничение текущей короткой STM-команды: в режиме памяти можно передать "
|
||
"либо \"размах + число точек\", либо \"пауза + число точек\"."
|
||
)
|
||
return tab
|
||
|
||
|
||
def _build_ad9833_tab(owner) -> QWidget:
|
||
tab = QWidget()
|
||
layout = QFormLayout(tab)
|
||
layout.setHorizontalSpacing(12)
|
||
layout.setVerticalSpacing(8)
|
||
|
||
owner._ad9833_enable = QCheckBox("Подать сигнал на выход")
|
||
owner._ad9833_enable.setChecked(True)
|
||
owner._ad9833_shape = QComboBox()
|
||
owner._ad9833_shape.addItem("Синус", "sine")
|
||
owner._ad9833_shape.addItem("Треугольник", "triangle")
|
||
owner._ad9833_shape.setCurrentIndex(1)
|
||
owner._ad9833_frequency_hz = _int_spinbox(
|
||
AD9833_OUTPUT_FREQ_MIN_HZ,
|
||
AD9833_OUTPUT_FREQ_MAX_HZ,
|
||
DEFAULT_AD9833_FREQUENCY_HZ,
|
||
suffix=" Гц",
|
||
)
|
||
owner._ad9833_frequency_hz.setSingleStep(1000)
|
||
owner._ad9833_frequency_hz.setGroupSeparatorShown(True)
|
||
owner._ad9833_frequency_hz.valueChanged.connect(owner._update_ad9833_preview)
|
||
owner._ad9833_word_preview = QLabel("Внутренний код: —")
|
||
owner._ad9833_word_preview.setObjectName("valueLabel")
|
||
|
||
note = QLabel(
|
||
f"AD9833 тактируется от {AD9833_MCLK_HZ:,} Гц. "
|
||
f"Поэтому здесь задаётся сразу частота сигнала в герцах. "
|
||
f"Рабочий диапазон интерфейса: {AD9833_OUTPUT_FREQ_MIN_HZ:,}..{AD9833_OUTPUT_FREQ_MAX_HZ:,} Гц "
|
||
f"(до половины тактовой частоты). Внутренний код рассчитывается автоматически."
|
||
)
|
||
note.setWordWrap(True)
|
||
note.setObjectName("captionLabel")
|
||
|
||
layout.addRow(owner._ad9833_enable)
|
||
layout.addRow("Форма сигнала", owner._ad9833_shape)
|
||
layout.addRow(
|
||
f"Частота сигнала ({AD9833_OUTPUT_FREQ_MIN_HZ:,}..{AD9833_OUTPUT_FREQ_MAX_HZ:,} Гц)",
|
||
owner._ad9833_frequency_hz,
|
||
)
|
||
layout.addRow("Внутренний код AD9833", owner._ad9833_word_preview)
|
||
layout.addRow(note)
|
||
|
||
owner._apply_ad9833_button = _expanding_button("Применить настройки генератора", primary=True)
|
||
owner._apply_ad9833_button.clicked.connect(owner._on_apply_ad9833)
|
||
layout.addRow(owner._apply_ad9833_button)
|
||
owner._ad9833_frequency_hz.setToolTip(
|
||
"Частота выходного сигнала в герцах. "
|
||
"Интерфейс ограничен диапазоном 0..10 МГц для тактовой частоты 20 МГц."
|
||
)
|
||
owner._ad9833_word_preview.setToolTip(
|
||
"28-битный frequency word, который будет автоматически передан в AD9833."
|
||
)
|
||
return tab
|
||
|
||
|
||
def _build_aux_tab(owner) -> QWidget:
|
||
tab = QWidget()
|
||
layout = QVBoxLayout(tab)
|
||
layout.setSpacing(10)
|
||
|
||
dac_group = QGroupBox("Аналоговый выход STM32 (PA4)")
|
||
dac_layout = QFormLayout(dac_group)
|
||
dac_layout.setHorizontalSpacing(12)
|
||
dac_layout.setVerticalSpacing(8)
|
||
|
||
dac_note = QLabel(
|
||
"Это встроенный 12-битный ЦАП микроконтроллера STM32. "
|
||
"Диапазон кода: 0..4095. Это соответствует примерно 0..Vref+, "
|
||
"то есть обычно около 0..3.3 В."
|
||
)
|
||
dac_note.setWordWrap(True)
|
||
dac_note.setObjectName("captionLabel")
|
||
|
||
owner._stm32_dac_enable = QCheckBox("Подать напряжение на выход")
|
||
owner._stm32_dac_enable.setChecked(True)
|
||
owner._stm32_dac_code = _int_spinbox(
|
||
STM32_DAC_CODE_MIN,
|
||
STM32_DAC_CODE_MAX,
|
||
DEFAULT_STM32_DAC_CODE,
|
||
)
|
||
owner._apply_stm32_dac_button = _expanding_button("Применить уровень выхода", primary=True)
|
||
owner._apply_stm32_dac_button.clicked.connect(owner._on_apply_stm32_dac)
|
||
dac_layout.addRow(dac_note)
|
||
dac_layout.addRow(owner._stm32_dac_enable)
|
||
dac_layout.addRow("Уровень выхода (0..4095)", owner._stm32_dac_code)
|
||
dac_layout.addRow(owner._apply_stm32_dac_button)
|
||
owner._stm32_dac_code.setToolTip(
|
||
"Код встроенного 12-битного ЦАП STM32. "
|
||
"0 = примерно 0 В, 4095 = примерно верхний предел питания ЦАП "
|
||
"(обычно около 3.3 В)."
|
||
)
|
||
|
||
ds_group = QGroupBox("Цифровой подстроечный резистор DS1809")
|
||
ds_layout = QFormLayout(ds_group)
|
||
ds_layout.setHorizontalSpacing(12)
|
||
ds_layout.setVerticalSpacing(8)
|
||
owner._ds1809_direction = QComboBox()
|
||
owner._ds1809_direction.addItem("Увеличить", "inc")
|
||
owner._ds1809_direction.addItem("Уменьшить", "dec")
|
||
owner._ds1809_count = _int_spinbox(DS1809_COUNT_MIN, DS1809_COUNT_MAX, DEFAULT_DS1809_COUNT)
|
||
owner._ds1809_pulse_ms = _int_spinbox(
|
||
DS1809_PULSE_MS_MIN,
|
||
DS1809_PULSE_MS_MAX,
|
||
DEFAULT_DS1809_PULSE_MS,
|
||
suffix=" мс",
|
||
)
|
||
owner._pulse_ds1809_button = _expanding_button("Сделать шаги резистора", primary=True)
|
||
owner._pulse_ds1809_button.clicked.connect(owner._on_pulse_ds1809)
|
||
owner._ds1809_profile_apply = QCheckBox("Сохранять абсолютную позицию в профиль")
|
||
owner._ds1809_profile_apply.setChecked(True)
|
||
owner._ds1809_profile_position = _int_spinbox(
|
||
DS1809_PROFILE_POSITION_MIN,
|
||
DS1809_PROFILE_POSITION_MAX,
|
||
DEFAULT_DS1809_PROFILE_POSITION,
|
||
)
|
||
ds_layout.addRow("Куда менять", owner._ds1809_direction)
|
||
ds_layout.addRow("Число шагов", owner._ds1809_count)
|
||
ds_layout.addRow("Длительность шага", owner._ds1809_pulse_ms)
|
||
ds_layout.addRow(owner._ds1809_profile_apply)
|
||
ds_layout.addRow("Позиция для профиля (от минимума)", owner._ds1809_profile_position)
|
||
ds_layout.addRow(owner._pulse_ds1809_button)
|
||
owner._ds1809_count.setToolTip("На сколько шагов изменить цифровой резистор.")
|
||
owner._ds1809_pulse_ms.setToolTip("Сколько миллисекунд длится один управляющий импульс.")
|
||
owner._ds1809_profile_apply.setToolTip(
|
||
"Если флажок включён, при сохранении профиля прошивка сначала загонит DS1809 в минимум, "
|
||
"а затем поднимет его до указанной позиции."
|
||
)
|
||
owner._ds1809_profile_position.setToolTip(
|
||
"Абсолютная позиция DS1809 относительно минимального положения. "
|
||
"Используется только при сохранении профиля на SD."
|
||
)
|
||
|
||
layout.addWidget(dac_group)
|
||
layout.addWidget(ds_group)
|
||
layout.addStretch(1)
|
||
return tab
|
||
|
||
|
||
def _build_wave_tab(owner) -> QWidget:
|
||
tab = QWidget()
|
||
layout = QVBoxLayout(tab)
|
||
layout.setSpacing(10)
|
||
|
||
note = QLabel(
|
||
"Здесь можно вручную загрузить свою форму сигнала для AD9102. "
|
||
"Каждое число - это одна точка формы. Допустимый диапазон: от -8192 до 8191."
|
||
)
|
||
note.setWordWrap(True)
|
||
note.setObjectName("captionLabel")
|
||
layout.addWidget(note)
|
||
|
||
owner._wave_info_label = QLabel("Отсчётов: 0")
|
||
owner._wave_info_label.setObjectName("valueLabel")
|
||
layout.addWidget(owner._wave_info_label)
|
||
|
||
owner._wave_samples_box = QPlainTextEdit()
|
||
owner._wave_samples_box.setPlaceholderText("0 1024 2048 1024 0 -1024 -2048 -1024")
|
||
owner._wave_samples_box.setMinimumHeight(180)
|
||
owner._wave_samples_box.textChanged.connect(owner._on_wave_text_changed)
|
||
layout.addWidget(owner._wave_samples_box)
|
||
|
||
buttons = QWidget()
|
||
buttons_layout = QHBoxLayout(buttons)
|
||
buttons_layout.setContentsMargins(0, 0, 0, 0)
|
||
buttons_layout.setSpacing(8)
|
||
|
||
owner._load_wave_file_button = _expanding_button("Открыть файл")
|
||
owner._upload_wave_button = _expanding_button("Загрузить форму", primary=True)
|
||
owner._cancel_wave_button = _expanding_button("Отменить загрузку")
|
||
owner._load_wave_file_button.clicked.connect(owner._on_load_wave_file)
|
||
owner._upload_wave_button.clicked.connect(owner._on_upload_waveform)
|
||
owner._cancel_wave_button.clicked.connect(owner._on_cancel_waveform)
|
||
|
||
buttons_layout.addWidget(owner._load_wave_file_button)
|
||
buttons_layout.addWidget(owner._upload_wave_button)
|
||
buttons_layout.addWidget(owner._cancel_wave_button)
|
||
layout.addWidget(buttons)
|
||
return tab
|
||
|
||
|
||
def build_status_group(owner) -> QGroupBox:
|
||
"""Create status and telemetry labels."""
|
||
group = QGroupBox("Телеметрия и статус")
|
||
layout = QVBoxLayout(group)
|
||
layout.setSpacing(10)
|
||
|
||
owner._status_header = QLabel("Отключено")
|
||
owner._status_header.setObjectName("statusError")
|
||
layout.addWidget(owner._status_header)
|
||
|
||
grid = QGridLayout()
|
||
grid.setHorizontalSpacing(12)
|
||
grid.setVerticalSpacing(6)
|
||
|
||
rows = [
|
||
("Порт", "_port_value"),
|
||
("Статус", "_state_value"),
|
||
("Доп. код", "_detail_value"),
|
||
("ID сообщения", "_message_id_value"),
|
||
("Температура 1", "_telemetry_temp1"),
|
||
("Температура 2", "_telemetry_temp2"),
|
||
("Фотодиод 1", "_telemetry_current1"),
|
||
("Фотодиод 2", "_telemetry_current2"),
|
||
("Внешняя температура 1", "_telemetry_temp_ext1"),
|
||
("Внешняя температура 2", "_telemetry_temp_ext2"),
|
||
("Питание 3.3 В", "_telemetry_3v3"),
|
||
("Питание 5V1", "_telemetry_5v1"),
|
||
("Питание 5V2", "_telemetry_5v2"),
|
||
("Питание 7V0", "_telemetry_7v0"),
|
||
]
|
||
|
||
for row_index, (label_text, attr_name) in enumerate(rows):
|
||
label = QLabel(label_text)
|
||
label.setObjectName("captionLabel")
|
||
value = QLabel("—")
|
||
value.setObjectName("valueLabel")
|
||
setattr(owner, attr_name, value)
|
||
grid.addWidget(label, row_index, 0)
|
||
grid.addWidget(value, row_index, 1)
|
||
|
||
layout.addLayout(grid)
|
||
|
||
buttons = QWidget()
|
||
buttons_layout = QHBoxLayout(buttons)
|
||
buttons_layout.setContentsMargins(0, 0, 0, 0)
|
||
buttons_layout.setSpacing(8)
|
||
|
||
owner._reconnect_button = _expanding_button("Переподключить")
|
||
owner._reset_button = _expanding_button("Сброс")
|
||
owner._save_profile_button = _expanding_button("Сохранить профиль", primary=True)
|
||
owner._reconnect_button.clicked.connect(owner._on_reconnect)
|
||
owner._reset_button.clicked.connect(owner._on_reset_device)
|
||
owner._save_profile_button.clicked.connect(owner._on_save_profile)
|
||
buttons_layout.addWidget(owner._reconnect_button)
|
||
buttons_layout.addWidget(owner._reset_button)
|
||
layout.addWidget(buttons)
|
||
layout.addWidget(owner._save_profile_button)
|
||
return group
|
||
|
||
|
||
def build_log_group(owner) -> QGroupBox:
|
||
"""Create compact runtime log output."""
|
||
group = QGroupBox("Runtime Log")
|
||
layout = QVBoxLayout(group)
|
||
|
||
owner._log_box = QTextEdit()
|
||
owner._log_box.setObjectName("logBox")
|
||
owner._log_box.setReadOnly(True)
|
||
owner._log_box.setMinimumHeight(180)
|
||
layout.addWidget(owner._log_box)
|
||
return group
|