Checkpoint EEPROM, firmware analysis and instrument protocol updates
This commit is contained in:
@@ -8,6 +8,6 @@ Shared PySide6 terminal migrated from PySetTerminal-master/pysetterminal.
|
||||
- COM transport uses QtSerialPort. Embedded operations reuse the TMS2812 connection and configured controller address.
|
||||
- PEEK/POKE packet construction and response validation use the existing protocan native C ABI, with 16-bit TMS2812 values.
|
||||
- Macro files are stored under the application data directory, outside the source checkout.
|
||||
- PySide2 and pyserial are no longer required. A native set-protocol runtime remains required.
|
||||
- The standalone launcher uses PySide6; SETGUI Windows 7 uses its existing PySide2/Qt5 compatibility layer. Frozen SETGUI builds bundle Python and Qt. Pyserial is not required. A native set-protocol runtime remains required.
|
||||
|
||||
`build_and_clean.py` is the migrated original standalone packaging helper.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout, QGroupBox, QGridLayout, QLineEdit, QPushButton, QScrollArea
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout, QGroupBox, QGridLayout, QLineEdit, QPushButton, QScrollArea, QSizePolicy
|
||||
|
||||
def build_byte_page(self):
|
||||
self.byte_page = QScrollArea()
|
||||
@@ -13,18 +14,22 @@ def build_byte_page(self):
|
||||
panels = QHBoxLayout()
|
||||
for write in (True, False):
|
||||
group = QGroupBox("Запись" if write else "Чтение")
|
||||
group.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
|
||||
form = QGridLayout(group)
|
||||
address = QLineEdit("$1000")
|
||||
value = QLineEdit("0000" if write else "")
|
||||
binary = QLineEdit("0000 0000 0000 0000" if write else "")
|
||||
binary.setMinimumWidth(220)
|
||||
address.setFixedWidth(150)
|
||||
value.setFixedWidth(100)
|
||||
binary.setFixedWidth(240)
|
||||
value.setReadOnly(not write)
|
||||
binary.setReadOnly(not write)
|
||||
for row, (title, widget) in enumerate((("Адрес", address), ("Байт (hex)", value), ("Байт (bin)", binary))):
|
||||
form.addWidget(QLabel(title), row * 2, 0)
|
||||
form.addWidget(widget, row * 2 + 1, 0)
|
||||
form.addWidget(QLabel(title), row, 0)
|
||||
form.addWidget(widget, row, 1)
|
||||
button = QPushButton("Записать" if write else "Прочитать")
|
||||
form.addWidget(button, 6, 0)
|
||||
form.addWidget(button, 3, 1, Qt.AlignLeft)
|
||||
form.setColumnStretch(2, 1)
|
||||
if write:
|
||||
self.byte_write_address, self.byte_write_value, self.byte_write_binary = address, value, binary
|
||||
value.textEdited.connect(lambda text: self._sync_byte_value(text, 16))
|
||||
@@ -34,6 +39,7 @@ def build_byte_page(self):
|
||||
self.byte_read_address, self.byte_read_value, self.byte_read_binary = address, value, binary
|
||||
button.clicked.connect(lambda: self._start_byte(False))
|
||||
panels.addWidget(group)
|
||||
panels.addStretch()
|
||||
layout.addLayout(panels)
|
||||
status = QLabel("Нет данных от контроллера")
|
||||
status.setWordWrap(True)
|
||||
|
||||
@@ -229,6 +229,7 @@ class RawProtocolWidget(QWidget):
|
||||
self.mem_result_label = QLabel("---")
|
||||
mem_layout.addWidget(self.mem_result_label, 1, 3)
|
||||
|
||||
mem_layout.setColumnStretch(4, 1)
|
||||
mem_group.setLayout(mem_layout)
|
||||
layout.addWidget(mem_group)
|
||||
|
||||
@@ -238,6 +239,7 @@ class RawProtocolWidget(QWidget):
|
||||
|
||||
self.raw_send_edit = QLineEdit()
|
||||
self.raw_send_edit.setPlaceholderText("Введите hex данные для отправки")
|
||||
self.raw_send_edit.setMaximumWidth(640)
|
||||
raw_layout.addWidget(self.raw_send_edit)
|
||||
|
||||
hbox = QHBoxLayout()
|
||||
@@ -250,6 +252,7 @@ class RawProtocolWidget(QWidget):
|
||||
hbox.addWidget(self.raw_read_btn)
|
||||
|
||||
|
||||
hbox.addStretch()
|
||||
raw_layout.addLayout(hbox)
|
||||
|
||||
self.raw_output = QTextEdit()
|
||||
@@ -260,7 +263,7 @@ class RawProtocolWidget(QWidget):
|
||||
|
||||
self.raw_clear_btn = QPushButton("Очистить логи")
|
||||
self.raw_clear_btn.clicked.connect(self.clear_logs)
|
||||
raw_layout.addWidget(self.raw_clear_btn)
|
||||
raw_layout.addWidget(self.raw_clear_btn, 0, Qt.AlignLeft)
|
||||
|
||||
raw_group.setLayout(raw_layout)
|
||||
layout.addWidget(raw_group)
|
||||
|
||||
@@ -1,11 +1,56 @@
|
||||
import json
|
||||
import os
|
||||
import weakref
|
||||
from functools import partial
|
||||
from copy import deepcopy
|
||||
from PySide6.QtWidgets import *
|
||||
from PySide6.QtCore import *
|
||||
from PySide6.QtGui import *
|
||||
|
||||
|
||||
class _MacroActionIcon(QIconEngine):
|
||||
"""Scalable action icons, painted with the button's current theme color."""
|
||||
def __init__(self, action, button):
|
||||
super().__init__()
|
||||
self.action = action
|
||||
self.button = weakref.ref(button)
|
||||
|
||||
def clone(self):
|
||||
return _MacroActionIcon(self.action, self.button())
|
||||
|
||||
def paint(self, painter, rect, mode, state):
|
||||
button = self.button()
|
||||
palette = button.palette() if button is not None else QApplication.palette()
|
||||
group = QPalette.Disabled if mode == QIcon.Disabled else QPalette.Active
|
||||
painter.save()
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
side = min(rect.width(), rect.height())
|
||||
painter.translate(rect.x() + (rect.width() - side) / 2, rect.y() + (rect.height() - side) / 2)
|
||||
painter.scale(side / 24, side / 24)
|
||||
painter.setPen(QPen(palette.color(group, QPalette.ButtonText), 1.8, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
|
||||
painter.setBrush(Qt.NoBrush)
|
||||
if self.action == "edit":
|
||||
path = QPainterPath(QPointF(4, 16))
|
||||
for x, y in ((15, 5), (19, 9), (8, 20), (3, 21), (4, 16)):
|
||||
path.lineTo(x, y)
|
||||
painter.drawPath(path)
|
||||
painter.drawLine(QPointF(13, 7), QPointF(17, 11))
|
||||
else:
|
||||
painter.drawLine(QPointF(4, 6), QPointF(20, 6))
|
||||
painter.drawRoundedRect(QRectF(6, 6, 12, 15), 1.5, 1.5)
|
||||
painter.drawPolyline(QPolygonF([QPointF(9, 6), QPointF(9, 3), QPointF(15, 3), QPointF(15, 6)]))
|
||||
painter.drawLine(QPointF(10, 10), QPointF(10, 17))
|
||||
painter.drawLine(QPointF(14, 10), QPointF(14, 17))
|
||||
painter.restore()
|
||||
|
||||
def pixmap(self, size, mode, state):
|
||||
pixmap = QPixmap(size)
|
||||
pixmap.fill(Qt.transparent)
|
||||
painter = QPainter(pixmap)
|
||||
self.paint(painter, QRect(QPoint(), size), mode, state)
|
||||
painter.end()
|
||||
return pixmap
|
||||
|
||||
class SerialBus:
|
||||
"""ВЫСОКОУРОВНЕВЫЙ протокол для работы с серийной шиной (только через регистры)"""
|
||||
|
||||
@@ -320,12 +365,17 @@ class SerialTab(QWidget):
|
||||
|
||||
self.bus_write_btn = QPushButton("Записать в шину")
|
||||
self.bus_write_btn.clicked.connect(self.write_to_bus)
|
||||
bus_layout.addWidget(self.bus_write_btn, 1, 0, 1, 3)
|
||||
|
||||
self.bus_read_btn = QPushButton("Прочитать из шину")
|
||||
self.bus_read_btn.clicked.connect(self.read_from_bus)
|
||||
bus_layout.addWidget(self.bus_read_btn, 1, 3, 1, 3)
|
||||
bus_buttons = QHBoxLayout()
|
||||
bus_buttons.setSpacing(6)
|
||||
bus_buttons.addWidget(self.bus_write_btn)
|
||||
bus_buttons.addWidget(self.bus_read_btn)
|
||||
bus_buttons.addStretch(1)
|
||||
bus_layout.addLayout(bus_buttons, 1, 0, 1, 6)
|
||||
|
||||
bus_layout.setColumnStretch(6, 1)
|
||||
bus_group.setLayout(bus_layout)
|
||||
layout.addWidget(bus_group)
|
||||
|
||||
@@ -336,23 +386,34 @@ class SerialTab(QWidget):
|
||||
# Кнопка создания нового макроса
|
||||
self.create_macro_btn = QPushButton("Создать новый макрос")
|
||||
self.create_macro_btn.clicked.connect(self.create_macro)
|
||||
macros_layout.addWidget(self.create_macro_btn)
|
||||
self.stop_macro_btn = QPushButton("Остановить макрос")
|
||||
self.stop_macro_btn.setEnabled(False)
|
||||
self.stop_macro_btn.clicked.connect(self.stop_macro)
|
||||
macros_layout.addWidget(self.stop_macro_btn)
|
||||
macro_actions = QHBoxLayout()
|
||||
macro_actions.setSpacing(6)
|
||||
macro_actions.addWidget(self.create_macro_btn)
|
||||
macro_actions.addWidget(self.stop_macro_btn)
|
||||
macro_actions.addStretch(1)
|
||||
macros_layout.addLayout(macro_actions)
|
||||
for button in (self.bus_write_btn, self.bus_read_btn, self.create_macro_btn, self.stop_macro_btn):
|
||||
button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
|
||||
button.setFixedHeight(30)
|
||||
button.setStyleSheet("QPushButton { padding: 3px 10px; }")
|
||||
|
||||
# Прокручиваемая область для кнопок макросов
|
||||
self.macros_scroll_area = QScrollArea()
|
||||
self.macros_scroll_area.setWidgetResizable(True)
|
||||
# Устанавливаем фиксированную высоту, чтобы вместить 3-4 строки макросов
|
||||
self.macros_scroll_area.setMinimumHeight(150) # Высота для 3-4 строк
|
||||
self.macros_scroll_area.setMinimumHeight(120) # Высота для 3-4 строк
|
||||
|
||||
self.macros_scroll_content = QWidget()
|
||||
self.macros_scroll_layout = QGridLayout()
|
||||
self.macros_scroll_content.setLayout(self.macros_scroll_layout)
|
||||
self.macros_scroll_area.setWidget(self.macros_scroll_content)
|
||||
self.macros_scroll_layout.setAlignment(Qt.AlignTop)
|
||||
self.macros_scroll_layout.setAlignment(Qt.AlignTop | Qt.AlignLeft)
|
||||
self.macros_scroll_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.macros_scroll_layout.setHorizontalSpacing(8)
|
||||
self.macros_scroll_layout.setVerticalSpacing(6)
|
||||
macros_layout.addWidget(self.macros_scroll_area)
|
||||
|
||||
macros_group.setLayout(macros_layout)
|
||||
@@ -375,6 +436,9 @@ class SerialTab(QWidget):
|
||||
results_layout.addWidget(self.bus_result_label, 1, 1)
|
||||
|
||||
# Создаем фрейм для результатов
|
||||
results_layout.setColumnStretch(2, 1)
|
||||
self.bus_read_value_label.setMinimumWidth(180)
|
||||
self.bus_result_label.setMaximumWidth(640)
|
||||
results_frame = QFrame()
|
||||
results_frame.setFrameShape(QFrame.StyledPanel)
|
||||
results_frame.setLayout(results_layout)
|
||||
@@ -470,8 +534,7 @@ class SerialTab(QWidget):
|
||||
|
||||
def add_macro_button_with_index(self, macro, index):
|
||||
"""Добавить кнопку макроса в интерфейс с указанным индексом"""
|
||||
# Изменяем расчет строк и колонок для 3 колонок
|
||||
items_per_row = 3 # Теперь 3 кнопки в строке
|
||||
items_per_row = 2
|
||||
row = len(self.macro_buttons) // items_per_row
|
||||
col = len(self.macro_buttons) % items_per_row
|
||||
|
||||
@@ -479,18 +542,20 @@ class SerialTab(QWidget):
|
||||
container = QWidget()
|
||||
container_layout = QHBoxLayout()
|
||||
container_layout.setContentsMargins(0, 0, 0, 0)
|
||||
container_layout.setSpacing(4)
|
||||
container.setMaximumWidth(360)
|
||||
container.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
|
||||
|
||||
# Основная кнопка макроса
|
||||
macro_btn = QPushButton(macro['name'])
|
||||
macro_btn.setMinimumHeight(40)
|
||||
macro_btn.setFixedHeight(30)
|
||||
macro_btn.setMinimumWidth(120)
|
||||
macro_btn.setToolTip(macro["name"])
|
||||
macro_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #e0e0e0;
|
||||
padding: 3px 8px;
|
||||
}
|
||||
""")
|
||||
|
||||
@@ -498,14 +563,22 @@ class SerialTab(QWidget):
|
||||
macro_btn.clicked.connect(partial(self.execute_macro_by_index, index))
|
||||
|
||||
# Кнопка редактирования
|
||||
edit_btn = QPushButton("✎")
|
||||
edit_btn = QPushButton()
|
||||
edit_btn.setIcon(QIcon(_MacroActionIcon("edit", edit_btn)))
|
||||
edit_btn.setIconSize(QSize(16, 16))
|
||||
edit_btn.setFixedSize(30, 30)
|
||||
edit_btn.setStyleSheet("QPushButton { padding: 0; }")
|
||||
edit_btn.setAccessibleName("Редактировать макрос")
|
||||
edit_btn.setToolTip("Редактировать макрос")
|
||||
edit_btn.clicked.connect(partial(self.edit_macro_by_index, index))
|
||||
|
||||
# Кнопка удаления
|
||||
delete_btn = QPushButton("✕")
|
||||
delete_btn = QPushButton()
|
||||
delete_btn.setIcon(QIcon(_MacroActionIcon("delete", delete_btn)))
|
||||
delete_btn.setIconSize(QSize(16, 16))
|
||||
delete_btn.setFixedSize(30, 30)
|
||||
delete_btn.setStyleSheet("QPushButton { padding: 0; }")
|
||||
delete_btn.setAccessibleName("Удалить макрос")
|
||||
delete_btn.setToolTip("Удалить макрос")
|
||||
delete_btn.clicked.connect(partial(self.delete_macro, index))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user