Checkpoint EEPROM, firmware analysis and instrument protocol updates

This commit is contained in:
2026-10-08 21:51:44 +03:00
parent b0c0c05cfa
commit fe9b5a246e
24 changed files with 848 additions and 62 deletions
+1 -1
View File
@@ -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.
+11 -5
View File
@@ -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)
+4 -1
View File
@@ -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)
+88 -15
View File
@@ -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))