структурирован код debug_tools
доработана демо-терминалка для считывания tms переменных и встроена в DebugVarEdit
This commit is contained in:
		
							parent
							
								
									c94a7e711c
								
							
						
					
					
						commit
						f2c4b7b3cd
					
				
							
								
								
									
										
											BIN
										
									
								
								DebugVarEdit.exe
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								DebugVarEdit.exe
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							@ -7,6 +7,7 @@ import subprocess
 | 
			
		||||
import lxml.etree as ET
 | 
			
		||||
from generate_debug_vars import type_map, choose_type_map
 | 
			
		||||
from enum import IntEnum
 | 
			
		||||
from tms_debugvar_term import _DemoWindow
 | 
			
		||||
import threading
 | 
			
		||||
from generate_debug_vars import run_generate
 | 
			
		||||
import var_setup
 | 
			
		||||
@ -17,6 +18,7 @@ import scan_vars
 | 
			
		||||
import myXML
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from PySide2.QtWidgets import (
 | 
			
		||||
    QApplication, QWidget, QTableWidget, QTableWidgetItem,
 | 
			
		||||
    QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
 | 
			
		||||
@ -148,7 +150,18 @@ class VarEditor(QWidget):
 | 
			
		||||
        self.target_menu.addAction(self.action_tms)
 | 
			
		||||
        self.target_menu.addAction(self.action_stm)
 | 
			
		||||
 | 
			
		||||
        self.terminal_menu = QMenu("Открыть Терминал", menubar)
 | 
			
		||||
        
 | 
			
		||||
        self.action_terminal_tms = QAction("TMS DemoTerminal", self)
 | 
			
		||||
        self.action_terminal_modbus = QAction("Modbus DemoTerminal", self)
 | 
			
		||||
        self.action_terminal_tms.triggered.connect(lambda: self.open_terminal("TMS"))
 | 
			
		||||
        self.action_terminal_modbus.triggered.connect(lambda: self.open_terminal("MODBUS"))
 | 
			
		||||
 | 
			
		||||
        self.terminal_menu.addAction(self.action_terminal_tms)
 | 
			
		||||
        #self.terminal_menu.addAction(self.action_terminal_modbus)
 | 
			
		||||
 | 
			
		||||
        menubar.addMenu(self.target_menu)
 | 
			
		||||
        menubar.addMenu(self.terminal_menu)
 | 
			
		||||
 | 
			
		||||
        # Кнопка сохранения
 | 
			
		||||
        btn_save = QPushButton(build_title)
 | 
			
		||||
@ -178,7 +191,16 @@ class VarEditor(QWidget):
 | 
			
		||||
 | 
			
		||||
        self.setLayout(layout)
 | 
			
		||||
 | 
			
		||||
    def open_terminal(self, target):
 | 
			
		||||
        target = target.lower()
 | 
			
		||||
        
 | 
			
		||||
        if target == "tms":
 | 
			
		||||
            self.terminal_widget = _DemoWindow()  # _DemoWindow наследует QWidget
 | 
			
		||||
            self.terminal_widget.show()
 | 
			
		||||
        elif target == "modbus":
 | 
			
		||||
            a=1
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
    def on_target_selected(self, target):
 | 
			
		||||
        self.target_menu.setTitle(f'МК: {target}')
 | 
			
		||||
        self.settings.setValue("mcu_choosen", target)
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,12 @@
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import struct
 | 
			
		||||
import datetime
 | 
			
		||||
from PySide2 import QtCore, QtWidgets, QtSerialPort
 | 
			
		||||
from PySide2.QtCore import QTimer
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
# ------------------------------- Константы протокола ------------------------
 | 
			
		||||
WATCH_SERVICE_BIT = 0x8000
 | 
			
		||||
DEBUG_OK = 0  # ожидаемый код успешного чтения
 | 
			
		||||
SIGN_BIT_MASK = 0x80
 | 
			
		||||
FRAC_MASK_FULL = 0x7F      # если используем 7 бит дробной части
 | 
			
		||||
# ---------------------------------------------------------------- CRC util ---
 | 
			
		||||
def crc16_ibm(data: bytes, *, init=0xFFFF) -> int:
 | 
			
		||||
    """CRC16-IBM (aka CRC-16/ANSI, polynomial 0xA001 reflected)."""
 | 
			
		||||
@ -92,33 +94,30 @@ class Spoiler(QtWidgets.QWidget):
 | 
			
		||||
 | 
			
		||||
        self._ani_content.start()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# --------------------------- DebugTerminalWidget ---------------------------
 | 
			
		||||
class DebugTerminalWidget(QtWidgets.QWidget):
 | 
			
		||||
    nameRead = QtCore.Signal(int, int, int, str)  # index, status, iq, name
 | 
			
		||||
    valueRead = QtCore.Signal(int, int, int, int, float)  # index, status, iq, raw16, floatVal
 | 
			
		||||
    nameRead = QtCore.Signal(int, int, int, str)          # index, status, iq, name
 | 
			
		||||
    valueRead = QtCore.Signal(int, int, int, int, float)  # для одиночного
 | 
			
		||||
    valuesRead = QtCore.Signal(int, int, list, list, list, list)  # base, count, idx_list, iq_list, raw_list, float_list
 | 
			
		||||
    portOpened = QtCore.Signal(str)
 | 
			
		||||
    portClosed = QtCore.Signal(str)
 | 
			
		||||
    txBytes = QtCore.Signal(bytes)  # raw bytes sent
 | 
			
		||||
    rxBytes = QtCore.Signal(bytes)  # raw bytes received (frame only)
 | 
			
		||||
    txBytes = QtCore.Signal(bytes)
 | 
			
		||||
    rxBytes = QtCore.Signal(bytes)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parent=None, *,
 | 
			
		||||
                 start_byte=0x0A,
 | 
			
		||||
                 cmd_byte=0x44,
 | 
			
		||||
                 name_field_len=11,
 | 
			
		||||
                 signed=True,
 | 
			
		||||
                 iq_scaling=None,
 | 
			
		||||
                 read_timeout_ms=200,
 | 
			
		||||
                 read_timeout_ms=250,
 | 
			
		||||
                 auto_crc_check=True,
 | 
			
		||||
                 drop_if_busy=False,
 | 
			
		||||
                 replace_if_busy=True):
 | 
			
		||||
        super().__init__(parent)
 | 
			
		||||
        self.start_byte = start_byte
 | 
			
		||||
        self.device_addr = start_byte
 | 
			
		||||
        self.cmd_byte = cmd_byte
 | 
			
		||||
        self.name_field_len = name_field_len
 | 
			
		||||
        self.signed = signed
 | 
			
		||||
        self.read_timeout_ms = read_timeout_ms
 | 
			
		||||
        self.auto_crc_check = auto_crc_check
 | 
			
		||||
 | 
			
		||||
        # lockstep policy flags
 | 
			
		||||
        self._drop_if_busy = drop_if_busy
 | 
			
		||||
        self._replace_if_busy = replace_if_busy
 | 
			
		||||
 | 
			
		||||
@ -127,159 +126,173 @@ class DebugTerminalWidget(QtWidgets.QWidget):
 | 
			
		||||
            iq_scaling[0] = 1.0
 | 
			
		||||
        self.iq_scaling = iq_scaling
 | 
			
		||||
 | 
			
		||||
        # Serial port ---------------------------------------------------------
 | 
			
		||||
        # Serial
 | 
			
		||||
        self.serial = QtSerialPort.QSerialPort(self)
 | 
			
		||||
        self.serial.setBaudRate(115200)
 | 
			
		||||
        self.serial.readyRead.connect(self._on_ready_read)
 | 
			
		||||
        self.serial.errorOccurred.connect(self._on_serial_error)
 | 
			
		||||
        self._index_change_timer = QtCore.QTimer(self)
 | 
			
		||||
        self._index_change_timer.setSingleShot(True)
 | 
			
		||||
        self._index_change_timer.timeout.connect(self._on_index_change_timeout)
 | 
			
		||||
        self._index_change_delay_ms = 200  # задержка перед отправкой запроса
 | 
			
		||||
 | 
			
		||||
        # RX state ------------------------------------------------------------
 | 
			
		||||
        # State
 | 
			
		||||
        self._rx_buf = bytearray()
 | 
			
		||||
        self._waiting_name = False
 | 
			
		||||
        self._expected_min_len = 0
 | 
			
		||||
        self._expected_exact_len = None  # if known exactly
 | 
			
		||||
        self._busy = False
 | 
			
		||||
        self._pending_cmd = None      # (frame, meta)
 | 
			
		||||
        self._txn_meta = None         # {'service':bool,'index':int,'varqnt':int,'chain':...}
 | 
			
		||||
 | 
			
		||||
        # Lockstep tx/rx state ------------------------------------------------
 | 
			
		||||
        self._busy = False         # True => запрос отправлен, ждём ответ/таймаут
 | 
			
		||||
        self._pending_cmd = None   # (frame, is_name, index) ожидающий отправки
 | 
			
		||||
        self._active_index = None  # индекс текущего запроса (для сигналов)
 | 
			
		||||
 | 
			
		||||
        # Timer for per-transaction timeout ----------------------------------
 | 
			
		||||
        self._txn_timer = QtCore.QTimer(self)
 | 
			
		||||
        self._txn_timer.setSingleShot(True)
 | 
			
		||||
        self._txn_timer.timeout.connect(self._on_txn_timeout)
 | 
			
		||||
 | 
			
		||||
        # Polling timer -------------------------------------------------------
 | 
			
		||||
        self._poll_timer = QtCore.QTimer(self)
 | 
			
		||||
        self._poll_timer.timeout.connect(self._on_poll_timeout)
 | 
			
		||||
        self._polling = False
 | 
			
		||||
 | 
			
		||||
        # Кэш: index -> (status, iq, name)
 | 
			
		||||
        self._name_cache = {}
 | 
			
		||||
 | 
			
		||||
        # Очередь требуемых service индексов перед чтением блока
 | 
			
		||||
        self._service_queue = []               # список индексов
 | 
			
		||||
        self._pending_data_after_services = None  # (base, count)
 | 
			
		||||
 | 
			
		||||
        self._build_ui()
 | 
			
		||||
 | 
			
		||||
        self.btn_open.clicked.connect(self._open_close_port)
 | 
			
		||||
        self.btn_refresh.clicked.connect(self.set_available_ports)
 | 
			
		||||
        self.btn_read_name.clicked.connect(self.request_name)
 | 
			
		||||
        self.btn_read_value.clicked.connect(self.request_value)
 | 
			
		||||
        self.btn_poll.clicked.connect(self._toggle_polling)
 | 
			
		||||
 | 
			
		||||
        self._connect_ui()
 | 
			
		||||
        self.set_available_ports()
 | 
			
		||||
 | 
			
		||||
    # ------------------------------------------------------------------ UI ---
 | 
			
		||||
    # ------------------------------ UI ----------------------------------
 | 
			
		||||
    def _build_ui(self):
 | 
			
		||||
        main_layout = QtWidgets.QVBoxLayout(self)
 | 
			
		||||
        main_layout.setContentsMargins(10, 10, 10, 10)
 | 
			
		||||
        main_layout.setSpacing(12)
 | 
			
		||||
 | 
			
		||||
        # --- Serial Port Group ---
 | 
			
		||||
        port_group = QtWidgets.QGroupBox("Serial Port")
 | 
			
		||||
        port_layout = QtWidgets.QHBoxLayout(port_group)
 | 
			
		||||
        layout = QtWidgets.QVBoxLayout(self)
 | 
			
		||||
 | 
			
		||||
        # --- Serial group ---
 | 
			
		||||
        g_serial = QtWidgets.QGroupBox("Serial Port")
 | 
			
		||||
        hs = QtWidgets.QHBoxLayout(g_serial)
 | 
			
		||||
        self.cmb_port = QtWidgets.QComboBox()
 | 
			
		||||
        self.btn_refresh = QtWidgets.QPushButton("Refresh")
 | 
			
		||||
        self.cmb_baud = QtWidgets.QComboBox()
 | 
			
		||||
        self.cmb_baud.addItems(["9600", "19200", "38400", "57600", "115200", "230400"])
 | 
			
		||||
        self.cmb_baud.addItems(["9600","19200","38400","57600","115200","230400"])
 | 
			
		||||
        self.cmb_baud.setCurrentText("115200")
 | 
			
		||||
        self.btn_open = QtWidgets.QPushButton("Open")
 | 
			
		||||
        hs.addWidget(QtWidgets.QLabel("Port:"))
 | 
			
		||||
        hs.addWidget(self.cmb_port, 1)
 | 
			
		||||
        hs.addWidget(self.btn_refresh)
 | 
			
		||||
        hs.addSpacing(10)
 | 
			
		||||
        hs.addWidget(QtWidgets.QLabel("Baud:"))
 | 
			
		||||
        hs.addWidget(self.cmb_baud)
 | 
			
		||||
        hs.addWidget(self.btn_open)
 | 
			
		||||
 | 
			
		||||
        port_layout.addWidget(QtWidgets.QLabel("Port:"))
 | 
			
		||||
        port_layout.addWidget(self.cmb_port, 1)
 | 
			
		||||
        port_layout.addWidget(self.btn_refresh)
 | 
			
		||||
        port_layout.addSpacing(20)
 | 
			
		||||
        port_layout.addWidget(QtWidgets.QLabel("Baud rate:"))
 | 
			
		||||
        port_layout.addWidget(self.cmb_baud, 0)
 | 
			
		||||
        port_layout.addWidget(self.btn_open)
 | 
			
		||||
 | 
			
		||||
        main_layout.addWidget(port_group)
 | 
			
		||||
 | 
			
		||||
        # --- Variable Control Group ---
 | 
			
		||||
        var_group = QtWidgets.QGroupBox("Watch Variable")
 | 
			
		||||
        var_layout = QtWidgets.QGridLayout(var_group)
 | 
			
		||||
        var_layout.setHorizontalSpacing(10)
 | 
			
		||||
        var_layout.setVerticalSpacing(6)
 | 
			
		||||
        # --- Watch group (будет растягиваться) ---
 | 
			
		||||
        g_watch = QtWidgets.QGroupBox("Watch Variables")
 | 
			
		||||
        grid = QtWidgets.QGridLayout(g_watch)
 | 
			
		||||
        grid.setHorizontalSpacing(8)
 | 
			
		||||
        grid.setVerticalSpacing(4)
 | 
			
		||||
 | 
			
		||||
        self.spin_index = QtWidgets.QSpinBox()
 | 
			
		||||
        self.spin_index.setRange(0, 0x7FFF)
 | 
			
		||||
        self.spin_index.setAccelerated(True)
 | 
			
		||||
        self.spin_index.valueChanged.connect(self._on_index_changed)
 | 
			
		||||
 | 
			
		||||
        self.chk_hex_index = QtWidgets.QCheckBox("Hex")
 | 
			
		||||
        self.chk_hex_index.stateChanged.connect(self._toggle_index_base)
 | 
			
		||||
 | 
			
		||||
        self.btn_read_name = QtWidgets.QPushButton("Read Name")
 | 
			
		||||
        self.btn_read_value = QtWidgets.QPushButton("Read Value")
 | 
			
		||||
        self.spin_count = QtWidgets.QSpinBox(); self.spin_count.setRange(1,255); self.spin_count.setValue(1)
 | 
			
		||||
        self.btn_read_service = QtWidgets.QPushButton("Read Name/Type")
 | 
			
		||||
        self.btn_read_values = QtWidgets.QPushButton("Read Value(s)")
 | 
			
		||||
        self.btn_poll = QtWidgets.QPushButton("Start Polling")
 | 
			
		||||
 | 
			
		||||
        self.spin_interval = QtWidgets.QSpinBox()
 | 
			
		||||
        self.spin_interval.setRange(100, 5000)
 | 
			
		||||
        self.spin_interval.setValue(500)
 | 
			
		||||
        self.spin_interval.setSuffix(" ms")
 | 
			
		||||
 | 
			
		||||
        self.edit_name = QtWidgets.QLineEdit()
 | 
			
		||||
        self.edit_name.setReadOnly(True)
 | 
			
		||||
 | 
			
		||||
        self.edit_value = QtWidgets.QLineEdit()
 | 
			
		||||
        self.edit_value.setReadOnly(True)
 | 
			
		||||
 | 
			
		||||
        self.spin_interval = QtWidgets.QSpinBox(); self.spin_interval.setRange(50,10000); self.spin_interval.setValue(500); self.spin_interval.setSuffix(" ms")
 | 
			
		||||
        self.chk_auto_service = QtWidgets.QCheckBox("Auto service before values if miss cache"); self.chk_auto_service.setChecked(True)
 | 
			
		||||
        self.chk_raw = QtWidgets.QCheckBox("Raw (no IQ scaling)")
 | 
			
		||||
        self.lbl_name = QtWidgets.QLineEdit(); self.lbl_name.setReadOnly(True)
 | 
			
		||||
        self.lbl_iq = QtWidgets.QLabel("-")
 | 
			
		||||
        self.chk_raw = QtWidgets.QCheckBox("Raw (no IQ format)")
 | 
			
		||||
        self.edit_single_value = QtWidgets.QLineEdit(); self.edit_single_value.setReadOnly(True)
 | 
			
		||||
 | 
			
		||||
        var_layout.addWidget(QtWidgets.QLabel("Index:"), 0, 0)
 | 
			
		||||
        var_layout.addWidget(self.spin_index, 0, 1)
 | 
			
		||||
        var_layout.addWidget(self.chk_hex_index, 0, 2)
 | 
			
		||||
        # --- Таблица: теперь 5 столбцов (если уже поменяли) ---
 | 
			
		||||
        self.tbl_values = QtWidgets.QTableWidget(0, 5)
 | 
			
		||||
        self.tbl_values.setHorizontalHeaderLabels(["Index","Name","IQ","Raw","Scaled"])
 | 
			
		||||
        self.tbl_values.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
 | 
			
		||||
                                    QtWidgets.QSizePolicy.Expanding)
 | 
			
		||||
        hh = self.tbl_values.horizontalHeader()
 | 
			
		||||
        hh.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
 | 
			
		||||
        hh.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
 | 
			
		||||
        hh.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
 | 
			
		||||
        hh.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
 | 
			
		||||
        hh.setSectionResizeMode(4, QtWidgets.QHeaderView.Stretch)
 | 
			
		||||
 | 
			
		||||
        var_layout.addWidget(self.btn_read_name, 1, 0)
 | 
			
		||||
        var_layout.addWidget(self.btn_read_value, 1, 1)
 | 
			
		||||
        var_layout.addWidget(self.btn_poll, 1, 2)
 | 
			
		||||
        vh = self.tbl_values.verticalHeader()
 | 
			
		||||
        vh.setVisible(False)
 | 
			
		||||
 | 
			
		||||
        var_layout.addWidget(QtWidgets.QLabel("Interval:"), 2, 0)
 | 
			
		||||
        var_layout.addWidget(self.spin_interval, 2, 1)
 | 
			
		||||
        r = 0
 | 
			
		||||
        grid.addWidget(QtWidgets.QLabel("Base Index:"), r, 0)
 | 
			
		||||
        grid.addWidget(self.spin_index, r, 1)
 | 
			
		||||
        grid.addWidget(self.chk_hex_index, r, 2); r += 1
 | 
			
		||||
 | 
			
		||||
        var_layout.addWidget(QtWidgets.QLabel("Name:"), 3, 0)
 | 
			
		||||
        var_layout.addWidget(self.edit_name, 3, 1, 1, 2)
 | 
			
		||||
        grid.addWidget(QtWidgets.QLabel("Count:"), r, 0)
 | 
			
		||||
        grid.addWidget(self.spin_count, r, 1); r += 1
 | 
			
		||||
 | 
			
		||||
        var_layout.addWidget(QtWidgets.QLabel("Value:"), 4, 0)
 | 
			
		||||
        var_layout.addWidget(self.edit_value, 4, 1, 1, 2)
 | 
			
		||||
        grid.addWidget(self.btn_read_service, r, 0)
 | 
			
		||||
        grid.addWidget(self.btn_read_values, r, 1)
 | 
			
		||||
        grid.addWidget(self.btn_poll, r, 2); r += 1
 | 
			
		||||
 | 
			
		||||
        var_layout.addWidget(QtWidgets.QLabel("IQ:"), 5, 0)
 | 
			
		||||
        var_layout.addWidget(self.lbl_iq, 5, 1)
 | 
			
		||||
        var_layout.addWidget(self.chk_raw, 5, 2)
 | 
			
		||||
        grid.addWidget(QtWidgets.QLabel("Interval:"), r, 0)
 | 
			
		||||
        grid.addWidget(self.spin_interval, r, 1)
 | 
			
		||||
        grid.addWidget(self.chk_auto_service, r, 2); r += 1
 | 
			
		||||
 | 
			
		||||
        main_layout.addWidget(var_group)
 | 
			
		||||
        
 | 
			
		||||
        # --- Collapsible UART Log ---
 | 
			
		||||
        grid.addWidget(QtWidgets.QLabel("Name:"), r, 0)
 | 
			
		||||
        grid.addWidget(self.lbl_name, r, 1, 1, 2); r += 1
 | 
			
		||||
 | 
			
		||||
        grid.addWidget(QtWidgets.QLabel("IQ:"), r, 0)
 | 
			
		||||
        grid.addWidget(self.lbl_iq, r, 1)
 | 
			
		||||
        grid.addWidget(self.chk_raw, r, 2); r += 1
 | 
			
		||||
 | 
			
		||||
        grid.addWidget(QtWidgets.QLabel("Single:"), r, 0)
 | 
			
		||||
        grid.addWidget(self.edit_single_value, r, 1, 1, 2); r += 1
 | 
			
		||||
 | 
			
		||||
        grid.addWidget(QtWidgets.QLabel("Array Values:"), r, 0); r += 1
 | 
			
		||||
 | 
			
		||||
        # --- Строка с таблицей, назначаем stretch ---
 | 
			
		||||
        grid.addWidget(self.tbl_values, r, 0, 1, 3)
 | 
			
		||||
        grid.setRowStretch(r, 1)   # таблица тянется
 | 
			
		||||
        # Все предыдущие строки по умолчанию имеют stretch=0
 | 
			
		||||
        # Можно явно grid.setRowStretch(i, 0) при желании
 | 
			
		||||
 | 
			
		||||
        # --- Добавляем группы в главный layout ---
 | 
			
		||||
        layout.addWidget(g_serial, 0)     # не растягивается (stretch=0)
 | 
			
		||||
        layout.addWidget(g_watch, 1)      # растягивается (stretch=1)
 | 
			
		||||
 | 
			
		||||
        # --- UART Log (минимальная высота) ---
 | 
			
		||||
        self.log_spoiler = Spoiler("UART Log", animationDuration=300, parent=self)
 | 
			
		||||
        self.log_spoiler.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
 | 
			
		||||
                                    QtWidgets.QSizePolicy.Minimum)
 | 
			
		||||
 | 
			
		||||
        log_layout = QtWidgets.QVBoxLayout()
 | 
			
		||||
        self.txt_log = QtWidgets.QTextEdit()
 | 
			
		||||
        self.txt_log.setReadOnly(True)
 | 
			
		||||
        self.txt_log.setFontFamily("Courier")
 | 
			
		||||
        self.txt_log.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
 | 
			
		||||
                                QtWidgets.QSizePolicy.Minimum)
 | 
			
		||||
        log_layout.addWidget(self.txt_log)
 | 
			
		||||
 | 
			
		||||
        self.log_spoiler.setContentLayout(log_layout)
 | 
			
		||||
        main_layout.addWidget(self.log_spoiler, 1)
 | 
			
		||||
 | 
			
		||||
        # Добавляем лог последним, но без stretch (0)
 | 
			
		||||
        layout.addWidget(self.log_spoiler, 0)
 | 
			
		||||
 | 
			
		||||
    def _toggle_log_panel(self, checked):
 | 
			
		||||
        if checked:
 | 
			
		||||
            self.toggle_log_btn.setArrowType(QtCore.Qt.DownArrow)
 | 
			
		||||
            self.log_panel.show()
 | 
			
		||||
        else:
 | 
			
		||||
            self.toggle_log_btn.setArrowType(QtCore.Qt.RightArrow)
 | 
			
		||||
            self.log_panel.hide()
 | 
			
		||||
    # ----------------------------------------------------------- Port mgmt ---
 | 
			
		||||
        # Строчки распределения: g_watch = 1, остальное = 0
 | 
			
		||||
        # Если хочешь принудительно:
 | 
			
		||||
        # layout.setStretchFactor(g_serial, 0)  # PySide2: нет прямого метода, можно:
 | 
			
		||||
        layout.setStretch(layout.indexOf(g_serial), 0)
 | 
			
		||||
        layout.setStretch(layout.indexOf(g_watch), 1)
 | 
			
		||||
        layout.setStretch(layout.indexOf(self.log_spoiler), 0)
 | 
			
		||||
 | 
			
		||||
    def _connect_ui(self):
 | 
			
		||||
        self.btn_refresh.clicked.connect(self.set_available_ports)
 | 
			
		||||
        self.btn_open.clicked.connect(self._open_close_port)
 | 
			
		||||
        self.btn_read_service.clicked.connect(self.request_service_single)
 | 
			
		||||
        self.btn_read_values.clicked.connect(self.request_values)
 | 
			
		||||
        self.btn_poll.clicked.connect(self._toggle_polling)
 | 
			
		||||
        self.chk_hex_index.stateChanged.connect(self._toggle_index_base)
 | 
			
		||||
 | 
			
		||||
    # ----------------------------- SERIAL MGMT ----------------------------
 | 
			
		||||
    def set_available_ports(self):
 | 
			
		||||
        """Enumerate COM ports and repopulate combo box."""
 | 
			
		||||
        current = self.cmb_port.currentText()
 | 
			
		||||
        cur = self.cmb_port.currentText()
 | 
			
		||||
        self.cmb_port.blockSignals(True)
 | 
			
		||||
        self.cmb_port.clear()
 | 
			
		||||
        for info in QtSerialPort.QSerialPortInfo.availablePorts():
 | 
			
		||||
            self.cmb_port.addItem(info.portName())
 | 
			
		||||
        if current:
 | 
			
		||||
            ix = self.cmb_port.findText(current)
 | 
			
		||||
        if cur:
 | 
			
		||||
            ix = self.cmb_port.findText(cur)
 | 
			
		||||
            if ix >= 0:
 | 
			
		||||
                self.cmb_port.setCurrentIndex(ix)
 | 
			
		||||
        self.cmb_port.blockSignals(False)
 | 
			
		||||
@ -292,308 +305,395 @@ class DebugTerminalWidget(QtWidgets.QWidget):
 | 
			
		||||
            self._log(f"[PORT] Closed {name}")
 | 
			
		||||
            self.portClosed.emit(name)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        port_name = self.cmb_port.currentText()
 | 
			
		||||
        if not port_name:
 | 
			
		||||
        port = self.cmb_port.currentText()
 | 
			
		||||
        if not port:
 | 
			
		||||
            self._log("[ERR] No port selected")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        baud = int(self.cmb_baud.currentText())
 | 
			
		||||
        self.serial.setPortName(port_name)
 | 
			
		||||
        self.serial.setBaudRate(baud)
 | 
			
		||||
        self.serial.setPortName(port)
 | 
			
		||||
        self.serial.setBaudRate(int(self.cmb_baud.currentText()))
 | 
			
		||||
        if not self.serial.open(QtCore.QIODevice.ReadWrite):
 | 
			
		||||
            self._log(f"[ERR] Failed to open {port_name}: {self.serial.errorString()}")
 | 
			
		||||
            self._log(f"[ERR] Open fail {port}: {self.serial.errorString()}")
 | 
			
		||||
            return
 | 
			
		||||
        self.btn_open.setText("Close")
 | 
			
		||||
        self._log(f"[PORT] Opened {port_name} @ {baud}")
 | 
			
		||||
        self.portOpened.emit(port_name)
 | 
			
		||||
        self._log(f"[PORT] Opened {port}")
 | 
			
		||||
        self.portOpened.emit(port)
 | 
			
		||||
 | 
			
		||||
    # --------------------------------------------------------- Frame build ---
 | 
			
		||||
    def _build_request(self, index: int, read_name: bool) -> bytes:
 | 
			
		||||
        if read_name:
 | 
			
		||||
            dbg = 0x8000 | (index & 0x7FFF)
 | 
			
		||||
        else:
 | 
			
		||||
            dbg = index & 0x7FFF
 | 
			
		||||
    # ---------------------------- FRAME BUILD -----------------------------
 | 
			
		||||
    def _build_request(self, index: int, *, service: bool, varqnt: int) -> bytes:
 | 
			
		||||
        dbg = index & 0x7FFF
 | 
			
		||||
        if service:
 | 
			
		||||
            dbg |= WATCH_SERVICE_BIT
 | 
			
		||||
        hi = (dbg >> 8) & 0xFF
 | 
			
		||||
        lo = dbg & 0xFF
 | 
			
		||||
        return bytes([self.start_byte & 0xFF, self.cmd_byte & 0xFF, hi, lo])
 | 
			
		||||
        q = varqnt & 0xFF
 | 
			
		||||
        payload = bytes([self.device_addr & 0xFF, self.cmd_byte & 0xFF, hi, lo, q])
 | 
			
		||||
        crc = crc16_ibm(payload)
 | 
			
		||||
        return payload + bytes([crc & 0xFF, (crc >> 8) & 0xFF])
 | 
			
		||||
 | 
			
		||||
    # ------------------------------- PUBLIC API (safe single outstanding) ---
 | 
			
		||||
    def request_name(self):
 | 
			
		||||
        self._issue_command(is_name=True)
 | 
			
		||||
    # ----------------------------- PUBLIC API -----------------------------
 | 
			
		||||
    def request_service_single(self):
 | 
			
		||||
        idx = int(self.spin_index.value())
 | 
			
		||||
        self._enqueue_or_start(idx, service=True, varqnt=0)
 | 
			
		||||
 | 
			
		||||
    def request_value(self):
 | 
			
		||||
        self._issue_command(is_name=False)
 | 
			
		||||
 | 
			
		||||
    def _issue_command(self, *, is_name: bool):
 | 
			
		||||
        index = int(self.spin_index.value())
 | 
			
		||||
        frame = self._build_request(index, is_name)
 | 
			
		||||
    def request_values(self):
 | 
			
		||||
        base = int(self.spin_index.value())
 | 
			
		||||
        count = int(self.spin_count.value())
 | 
			
		||||
        needed = []
 | 
			
		||||
        if self.chk_auto_service.isChecked():
 | 
			
		||||
            for i in range(base, base+count):
 | 
			
		||||
                if i not in self._name_cache:
 | 
			
		||||
                    needed.append(i)
 | 
			
		||||
        if needed:
 | 
			
		||||
            self._service_queue = needed[:]  # копия
 | 
			
		||||
            self._pending_data_after_services = (base, count)
 | 
			
		||||
            self._log(f"[AUTO] Need service for {len(needed)} indices: {needed}")
 | 
			
		||||
            self._kick_service_queue()
 | 
			
		||||
        else:
 | 
			
		||||
            self._enqueue_or_start(base, service=False, varqnt=count)
 | 
			
		||||
 | 
			
		||||
    # -------------------------- SERVICE QUEUE FLOW ------------------------
 | 
			
		||||
    def _kick_service_queue(self):
 | 
			
		||||
        if self._busy:
 | 
			
		||||
            if self._drop_if_busy:
 | 
			
		||||
                self._log("[LOCKSTEP] Busy -> drop new request")
 | 
			
		||||
            return  # дождёмся завершения
 | 
			
		||||
        if self._service_queue:
 | 
			
		||||
            nxt = self._service_queue.pop(0)
 | 
			
		||||
            # не используем chain, просто по завершению снова вызовем _kick_service_queue
 | 
			
		||||
            self._enqueue_or_start(nxt, service=True, varqnt=0, queue_mode=True)
 | 
			
		||||
        elif self._pending_data_after_services:
 | 
			
		||||
            base, count = self._pending_data_after_services
 | 
			
		||||
            self._pending_data_after_services = None
 | 
			
		||||
            self._enqueue_or_start(base, service=False, varqnt=count)
 | 
			
		||||
 | 
			
		||||
    # ------------------------ TRANSACTION SCHEDULER -----------------------
 | 
			
		||||
    def _enqueue_or_start(self, index, service: bool, varqnt: int, chain_after=None, queue_mode=False):
 | 
			
		||||
        frame = self._build_request(index, service=service, varqnt=varqnt)
 | 
			
		||||
        meta = {'service': service, 'index': index, 'varqnt': varqnt, 'chain': chain_after, 'queue_mode': queue_mode}
 | 
			
		||||
        if self._busy:
 | 
			
		||||
            if self._drop_if_busy and not self._replace_if_busy:
 | 
			
		||||
                self._log("[LOCKSTEP] Busy -> drop")
 | 
			
		||||
                return
 | 
			
		||||
            if self._replace_if_busy:
 | 
			
		||||
                self._pending_cmd = (frame, is_name, index)
 | 
			
		||||
                self._log("[LOCKSTEP] Busy -> replaced pending request")
 | 
			
		||||
                self._pending_cmd = (frame, meta)
 | 
			
		||||
                self._log("[LOCKSTEP] Busy -> replaced pending")
 | 
			
		||||
            else:
 | 
			
		||||
                # queue disabled; ignore
 | 
			
		||||
                self._log("[LOCKSTEP] Busy -> ignore (no replace)")
 | 
			
		||||
                self._log("[LOCKSTEP] Busy -> ignore")
 | 
			
		||||
            return
 | 
			
		||||
        self._start_txn(frame, meta)
 | 
			
		||||
 | 
			
		||||
        # idle -> send immediately
 | 
			
		||||
        self._start_transaction(frame, is_name, index)
 | 
			
		||||
 | 
			
		||||
    # ------------------------------------------------------ TXN lifecycle ---
 | 
			
		||||
    def _start_transaction(self, frame: bytes, is_name: bool, index: int):
 | 
			
		||||
        """Mark busy, compute expected length, send frame, start timeout."""
 | 
			
		||||
    def _start_txn(self, frame: bytes, meta: dict):
 | 
			
		||||
        self._busy = True
 | 
			
		||||
        self._active_index = index
 | 
			
		||||
        self._waiting_name = is_name
 | 
			
		||||
        # Expected minimal len: hdr[4] + payload(name/val) + crc/trailer[4]
 | 
			
		||||
        if is_name:
 | 
			
		||||
            self._expected_min_len = 4 + self.name_field_len + 4
 | 
			
		||||
        else:
 | 
			
		||||
            self._expected_min_len = 4 + 2 + 4
 | 
			
		||||
        self._expected_exact_len = self._expected_min_len  # protocol fixed-size now
 | 
			
		||||
        self._txn_meta = meta
 | 
			
		||||
        self._rx_buf.clear()
 | 
			
		||||
        self._set_ui_busy(True)
 | 
			
		||||
        self._send(frame)
 | 
			
		||||
        self._txn_timer.start(self.read_timeout_ms)
 | 
			
		||||
 | 
			
		||||
    def _end_transaction(self):
 | 
			
		||||
        """Common exit path after parse or timeout."""
 | 
			
		||||
    def _end_txn(self):
 | 
			
		||||
        self._txn_timer.stop()
 | 
			
		||||
        queue_mode = False
 | 
			
		||||
        chain = None
 | 
			
		||||
        if self._txn_meta:
 | 
			
		||||
            queue_mode = self._txn_meta.get('queue_mode', False)
 | 
			
		||||
            chain = self._txn_meta.get('chain')
 | 
			
		||||
        self._txn_meta = None
 | 
			
		||||
        self._busy = False
 | 
			
		||||
        self._active_index = None
 | 
			
		||||
        self._expected_min_len = 0
 | 
			
		||||
        self._expected_exact_len = None
 | 
			
		||||
        self._rx_buf.clear()
 | 
			
		||||
        self._set_ui_busy(False)
 | 
			
		||||
        # if we have pending -> fire it now
 | 
			
		||||
 | 
			
		||||
        # Если был chain -> запустить его
 | 
			
		||||
        if chain:
 | 
			
		||||
            base, serv, q = chain
 | 
			
		||||
            self._enqueue_or_start(base, service=serv, varqnt=q)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if self._pending_cmd is not None:
 | 
			
		||||
            frame, is_name, index = self._pending_cmd
 | 
			
		||||
            frame, meta = self._pending_cmd
 | 
			
		||||
            self._pending_cmd = None
 | 
			
		||||
            # start immediately (no recursion issues; single-shot via singleShot)
 | 
			
		||||
            QtCore.QTimer.singleShot(0, lambda f=frame, n=is_name, i=index: self._start_transaction(f, n, i))
 | 
			
		||||
            QtCore.QTimer.singleShot(0, lambda f=frame,m=meta: self._start_txn(f,m))
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # Если это был элемент очереди service -> continue
 | 
			
		||||
        if queue_mode:
 | 
			
		||||
            QtCore.QTimer.singleShot(0, self._kick_service_queue)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
    def _on_txn_timeout(self):
 | 
			
		||||
        if not self._busy:
 | 
			
		||||
            return
 | 
			
		||||
        self._log("[TIMEOUT] Response not received in time; aborting transaction")
 | 
			
		||||
        # log any garbage that came in
 | 
			
		||||
        self._log("[TIMEOUT] No response")
 | 
			
		||||
        if self._rx_buf:
 | 
			
		||||
            self._log_frame(bytes(self._rx_buf), tx=False)
 | 
			
		||||
        self._end_transaction()
 | 
			
		||||
        self._end_txn()
 | 
			
		||||
 | 
			
		||||
    # --------------------------------------------------------------- TX/RX ---
 | 
			
		||||
    # ------------------------------- TX/RX ---------------------------------
 | 
			
		||||
    def _send(self, data: bytes):
 | 
			
		||||
        n = self.serial.write(data)
 | 
			
		||||
        if n != len(data):
 | 
			
		||||
            self._log(f"[ERR] Write incomplete: {n}/{len(data)}")
 | 
			
		||||
        w = self.serial.write(data)
 | 
			
		||||
        if w != len(data):
 | 
			
		||||
            self._log(f"[ERR] Write short {w}/{len(data)}")
 | 
			
		||||
        self.txBytes.emit(data)
 | 
			
		||||
        self._log_frame(data, tx=True)
 | 
			
		||||
 | 
			
		||||
    def _on_ready_read(self):
 | 
			
		||||
        if not self._busy:
 | 
			
		||||
            # unexpected data while idle -> just log & drop
 | 
			
		||||
            chunk = self.serial.readAll().data()
 | 
			
		||||
            if chunk:
 | 
			
		||||
                self._log("[WARN] RX while idle -> ignored")
 | 
			
		||||
                self._log_frame(chunk, tx=False)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self._rx_buf.extend(self.serial.readAll().data())
 | 
			
		||||
 | 
			
		||||
        # If exact length known and reached -> parse immediately (no wait for timeout)
 | 
			
		||||
        if self._expected_exact_len is not None and len(self._rx_buf) >= self._expected_exact_len:
 | 
			
		||||
            frame = bytes(self._rx_buf[:self._expected_exact_len])
 | 
			
		||||
            # log rx; if extra bytes remain we'll keep them for next txn (unlikely)
 | 
			
		||||
            self.rxBytes.emit(frame)
 | 
			
		||||
            self._log_frame(frame, tx=False)
 | 
			
		||||
            self._parse_response(frame)
 | 
			
		||||
            # discard everything consumed
 | 
			
		||||
            del self._rx_buf[:self._expected_exact_len]
 | 
			
		||||
        if not self._busy:
 | 
			
		||||
            if self._rx_buf:
 | 
			
		||||
                self._log("[WARN] Extra RX bytes after frame -> stash for next txn")
 | 
			
		||||
            self._end_transaction()
 | 
			
		||||
                self._log("[WARN] Data while idle -> drop")
 | 
			
		||||
                self._log_frame(bytes(self._rx_buf), tx=False)
 | 
			
		||||
                self._rx_buf.clear()
 | 
			
		||||
            return
 | 
			
		||||
        self._try_parse()
 | 
			
		||||
 | 
			
		||||
        # If only min len known: check >= min -> try parse; else keep waiting (timer still running)
 | 
			
		||||
        if self._expected_min_len and len(self._rx_buf) >= self._expected_min_len:
 | 
			
		||||
            frame = bytes(self._rx_buf)
 | 
			
		||||
    # ------------------------------- PARSING -------------------------------
 | 
			
		||||
    def _try_parse(self):
 | 
			
		||||
        if not self._txn_meta:
 | 
			
		||||
            return
 | 
			
		||||
        service = self._txn_meta['service']
 | 
			
		||||
        buf = self._rx_buf
 | 
			
		||||
        trailer_len = 4
 | 
			
		||||
        if service:
 | 
			
		||||
            if len(buf) < 7 + trailer_len:  # adr cmd vhi vlo status iq nameLen + trailer
 | 
			
		||||
                return
 | 
			
		||||
            name_len = buf[6]
 | 
			
		||||
            expected = 7 + name_len + trailer_len
 | 
			
		||||
            if len(buf) < expected:
 | 
			
		||||
                return
 | 
			
		||||
            frame = bytes(buf[:expected])
 | 
			
		||||
            del buf[:expected]
 | 
			
		||||
            self.rxBytes.emit(frame)
 | 
			
		||||
            self._log_frame(frame, tx=False)
 | 
			
		||||
            self._parse_response(frame)
 | 
			
		||||
            self._end_transaction()
 | 
			
		||||
            self._parse_service_frame(frame)
 | 
			
		||||
            self._end_txn()
 | 
			
		||||
        else:
 | 
			
		||||
            if len(buf) < 6 + trailer_len:  # adr cmd vhi vlo varqnt status + trailer
 | 
			
		||||
                return
 | 
			
		||||
            varqnt = buf[4]
 | 
			
		||||
            status = buf[5]
 | 
			
		||||
            if status != DEBUG_OK:
 | 
			
		||||
                expected = 8 + trailer_len  # + errIndex(2)
 | 
			
		||||
                if len(buf) < expected:
 | 
			
		||||
                    return
 | 
			
		||||
                frame = bytes(buf[:expected])
 | 
			
		||||
                del buf[:expected]
 | 
			
		||||
                self.rxBytes.emit(frame)
 | 
			
		||||
                self._log_frame(frame, tx=False)
 | 
			
		||||
                self._parse_data_frame(frame, error_mode=True)
 | 
			
		||||
                self._end_txn()
 | 
			
		||||
            else:
 | 
			
		||||
                expected = 6 + varqnt*2 + trailer_len
 | 
			
		||||
                if len(buf) < expected:
 | 
			
		||||
                    return
 | 
			
		||||
                frame = bytes(buf[:expected])
 | 
			
		||||
                del buf[:expected]
 | 
			
		||||
                self.rxBytes.emit(frame)
 | 
			
		||||
                self._log_frame(frame, tx=False)
 | 
			
		||||
                self._parse_data_frame(frame, error_mode=False)
 | 
			
		||||
                self._end_txn()
 | 
			
		||||
 | 
			
		||||
    def _check_crc(self, payload: bytes, crc_lo: int, crc_hi: int):
 | 
			
		||||
        if not self.auto_crc_check:
 | 
			
		||||
            return True
 | 
			
		||||
        crc_rx = (crc_hi << 8) | crc_lo
 | 
			
		||||
        crc_calc = crc16_ibm(payload)
 | 
			
		||||
        if crc_calc != crc_rx:
 | 
			
		||||
            self._log(f"[CRC FAIL] calc=0x{crc_calc:04X} rx=0x{crc_rx:04X}")
 | 
			
		||||
            return False
 | 
			
		||||
        self._log("[CRC OK]")
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _clear_service_bit(vhi, vlo):
 | 
			
		||||
        return ((vhi & 0x7F) << 8) | vlo
 | 
			
		||||
 | 
			
		||||
    def _parse_service_frame(self, frame: bytes):
 | 
			
		||||
        payload = frame[:-4]
 | 
			
		||||
        crc_lo, crc_hi = frame[-4], frame[-3]
 | 
			
		||||
        if len(payload) < 7:
 | 
			
		||||
            self._log("[ERR] Service frame too short")
 | 
			
		||||
            return
 | 
			
		||||
        # else: wait for more data or timeout
 | 
			
		||||
        self._check_crc(payload, crc_lo, crc_hi)
 | 
			
		||||
        adr, cmd, vhi, vlo, status, iq_raw, name_len = payload[:7]
 | 
			
		||||
        index = self._clear_service_bit(vhi, vlo)
 | 
			
		||||
        if len(payload) < 7 + name_len:
 | 
			
		||||
            self._log("[ERR] Service name truncated")
 | 
			
		||||
            return
 | 
			
		||||
        name_bytes = payload[7:7+name_len]
 | 
			
		||||
        name = name_bytes.decode(errors='replace')
 | 
			
		||||
 | 
			
		||||
        # ### PATCH: извлекаем признаки
 | 
			
		||||
        is_signed = (iq_raw & SIGN_BIT_MASK) != 0
 | 
			
		||||
        frac_bits = iq_raw & FRAC_MASK_FULL
 | 
			
		||||
 | 
			
		||||
        if status == DEBUG_OK:
 | 
			
		||||
            # Кэшируем расширенный кортеж
 | 
			
		||||
            self._name_cache[index] = (status, iq_raw, name, is_signed, frac_bits)
 | 
			
		||||
 | 
			
		||||
        self.nameRead.emit(index, status, iq_raw, name)
 | 
			
		||||
 | 
			
		||||
        if self.spin_count.value() == 1 and index == self.spin_index.value():
 | 
			
		||||
            if status == DEBUG_OK:
 | 
			
		||||
                self.lbl_name.setText(name)
 | 
			
		||||
                # Отображаем IQ как «число_дробных_бит + s/u»
 | 
			
		||||
                self.lbl_iq.setText(f"{frac_bits}{'s' if is_signed else 'u'}")
 | 
			
		||||
            else:
 | 
			
		||||
                self.lbl_name.setText('<err>')
 | 
			
		||||
                self.lbl_iq.setText('-')
 | 
			
		||||
 | 
			
		||||
        self._log(f"[SERVICE] idx={index} status={status} iq_raw=0x{iq_raw:02X} "
 | 
			
		||||
                f"sign={'S' if is_signed else 'U'} frac={frac_bits} name='{name}'")
 | 
			
		||||
 | 
			
		||||
    def _parse_data_frame(self, frame: bytes, *, error_mode: bool):
 | 
			
		||||
        payload = frame[:-4]
 | 
			
		||||
        crc_lo, crc_hi = frame[-4], frame[-3]
 | 
			
		||||
        if len(payload) < 6:
 | 
			
		||||
            self._log("[ERR] Data frame too short")
 | 
			
		||||
            return
 | 
			
		||||
        self._check_crc(payload, crc_lo, crc_hi)
 | 
			
		||||
        adr, cmd, vhi, vlo, varqnt, status = payload[:6]
 | 
			
		||||
        base = self._clear_service_bit(vhi, vlo)
 | 
			
		||||
        if error_mode:
 | 
			
		||||
            if len(payload) < 8:
 | 
			
		||||
                self._log("[ERR] Error frame truncated")
 | 
			
		||||
                return
 | 
			
		||||
            err_hi, err_lo = payload[6:8]
 | 
			
		||||
            bad_index = (err_hi << 8) | err_lo
 | 
			
		||||
            self._log(f"[DATA] ERROR status={status} bad_index={bad_index}")
 | 
			
		||||
            self.valueRead.emit(bad_index, status, 0, 0, float('nan'))
 | 
			
		||||
            self.valuesRead.emit(base, 0, [], [], [], [])
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # Нормальный кадр
 | 
			
		||||
        if len(payload) < 6 + varqnt*2:
 | 
			
		||||
            self._log("[ERR] Data payload truncated")
 | 
			
		||||
            return
 | 
			
		||||
        raw_vals = []
 | 
			
		||||
        pos = 6
 | 
			
		||||
        for _ in range(varqnt):
 | 
			
		||||
            hi = payload[pos]; lo = payload[pos+1]; pos += 2
 | 
			
		||||
            raw16 = (hi << 8) | lo
 | 
			
		||||
            raw_vals.append(raw16)  # пока храним как 0..65535
 | 
			
		||||
 | 
			
		||||
        idx_list = []
 | 
			
		||||
        iq_list = []
 | 
			
		||||
        name_list = []
 | 
			
		||||
        scaled_list = []
 | 
			
		||||
        display_raw_list = []  # для таблицы Raw (с учётом знака, если знак есть)
 | 
			
		||||
 | 
			
		||||
        for ofs, raw16 in enumerate(raw_vals):
 | 
			
		||||
            idx = base + ofs
 | 
			
		||||
            # В кэше теперь 5 элементов
 | 
			
		||||
            status_i, iq_raw, name_i, is_signed, frac_bits = self._name_cache.get(
 | 
			
		||||
                idx,
 | 
			
		||||
                (DEBUG_OK, 0, '', False, 0)
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # Приведение знака
 | 
			
		||||
            if is_signed and (raw16 & 0x8000):
 | 
			
		||||
                value_int = raw16 - 0x10000  # signed 16-bit
 | 
			
		||||
            else:
 | 
			
		||||
                value_int = raw16
 | 
			
		||||
 | 
			
		||||
            # Масштаб
 | 
			
		||||
            if self.chk_raw.isChecked():
 | 
			
		||||
                scale = 1.0
 | 
			
		||||
            else:
 | 
			
		||||
                # scale берём: если в словаре нет — вычисляем 2**frac_bits
 | 
			
		||||
                scale = self.iq_scaling.get(frac_bits, 2.0 ** frac_bits)
 | 
			
		||||
 | 
			
		||||
            scaled = value_int / scale
 | 
			
		||||
 | 
			
		||||
            idx_list.append(idx)
 | 
			
		||||
            iq_list.append(iq_raw)       # сырой байт (с битом знака)
 | 
			
		||||
            name_list.append(name_i)
 | 
			
		||||
            scaled_list.append(scaled)
 | 
			
		||||
            display_raw_list.append(value_int)
 | 
			
		||||
 | 
			
		||||
        # Populate table
 | 
			
		||||
        self._populate_table(idx_list, name_list, iq_list, display_raw_list, scaled_list)
 | 
			
		||||
 | 
			
		||||
        if varqnt == 1:
 | 
			
		||||
            self.edit_single_value.setText(
 | 
			
		||||
                str(display_raw_list[0]) if self.chk_raw.isChecked() else f"{scaled_list[0]:.6g}"
 | 
			
		||||
            )
 | 
			
		||||
            if idx_list[0] == self.spin_index.value():
 | 
			
		||||
                # Отобразим мета
 | 
			
		||||
                # Достаём из кэша снова (или можно из name_list/iq_list + пересчитать)
 | 
			
		||||
                _, iq_raw0, name0, is_signed0, frac0 = self._name_cache.get(idx_list[0], (DEBUG_OK, 0, '', False, 0))
 | 
			
		||||
                self.lbl_name.setText(name0)
 | 
			
		||||
                self.lbl_iq.setText(f"{frac0}{'s' if is_signed0 else 'u'}")
 | 
			
		||||
            self.valueRead.emit(idx_list[0], status, iq_list[0], display_raw_list[0], scaled_list[0])
 | 
			
		||||
        else:
 | 
			
		||||
            self.edit_single_value.setText("")
 | 
			
		||||
            self.valuesRead.emit(base, varqnt, idx_list, iq_list, display_raw_list, scaled_list)
 | 
			
		||||
 | 
			
		||||
        self._log(f"[DATA] base={base} q={varqnt} values={[f'{v:.6g}' for v in scaled_list] if not self.chk_raw.isChecked() else raw_vals}")
 | 
			
		||||
 | 
			
		||||
    def _populate_table(self, idxs, names, iqs, raws, scaled):
 | 
			
		||||
        self.tbl_values.setRowCount(len(idxs))
 | 
			
		||||
        for row, (idx, nm, iq_raw, rv, sv) in enumerate(zip(idxs, names, iqs, raws, scaled)):
 | 
			
		||||
            is_signed = (iq_raw & SIGN_BIT_MASK) != 0
 | 
			
		||||
            frac_bits = iq_raw & FRAC_MASK_FULL
 | 
			
		||||
            iq_disp = f"{frac_bits}{'s' if is_signed else 'u'}"
 | 
			
		||||
            raw_display = str(rv)
 | 
			
		||||
            scaled_display = raw_display if self.chk_raw.isChecked() else f"{sv:.6g}"
 | 
			
		||||
            items = [
 | 
			
		||||
                QtWidgets.QTableWidgetItem(str(idx)),
 | 
			
		||||
                QtWidgets.QTableWidgetItem(nm),
 | 
			
		||||
                QtWidgets.QTableWidgetItem(iq_disp),
 | 
			
		||||
                QtWidgets.QTableWidgetItem(raw_display),
 | 
			
		||||
                QtWidgets.QTableWidgetItem(scaled_display),
 | 
			
		||||
            ]
 | 
			
		||||
            for it in items:
 | 
			
		||||
                it.setFlags(it.flags() & ~QtCore.Qt.ItemIsEditable)
 | 
			
		||||
            for c, it in enumerate(items):
 | 
			
		||||
                self.tbl_values.setItem(row, c, it)
 | 
			
		||||
 | 
			
		||||
    # ------------------------------ POLLING --------------------------------
 | 
			
		||||
    def _toggle_polling(self):
 | 
			
		||||
        if self._polling:
 | 
			
		||||
            self._poll_timer.stop(); self._polling=False; self.btn_poll.setText("Start Polling"); self._log("[POLL] Stopped")
 | 
			
		||||
            self.btn_read_service.setEnabled(True)
 | 
			
		||||
            self.btn_read_values.setEnabled(True)
 | 
			
		||||
        else:
 | 
			
		||||
            self._poll_timer.start(self.spin_interval.value()); self._polling=True; self.btn_poll.setText("Stop Polling"); self._log(f"[POLL] Started interval={self.spin_interval.value()}ms")
 | 
			
		||||
            self.btn_read_service.setEnabled(False)
 | 
			
		||||
            self.btn_read_values.setEnabled(False)
 | 
			
		||||
 | 
			
		||||
    def _on_poll_timeout(self):
 | 
			
		||||
        if not self.serial.isOpen() or self._busy:
 | 
			
		||||
            return
 | 
			
		||||
        self.request_values()
 | 
			
		||||
 | 
			
		||||
    # ------------------------------ HELPERS --------------------------------
 | 
			
		||||
    def _toggle_index_base(self, st):
 | 
			
		||||
        val = self.spin_index.value()
 | 
			
		||||
        if st == QtCore.Qt.Checked:
 | 
			
		||||
            self.spin_index.setDisplayIntegerBase(16); self.spin_index.setPrefix("0x")
 | 
			
		||||
        else:
 | 
			
		||||
            self.spin_index.setDisplayIntegerBase(10); self.spin_index.setPrefix("")
 | 
			
		||||
        self.spin_index.setValue(val)
 | 
			
		||||
 | 
			
		||||
    def _set_ui_busy(self, busy: bool):
 | 
			
		||||
        if self._polling == False:
 | 
			
		||||
            self.btn_read_service.setEnabled(not busy)
 | 
			
		||||
            self.btn_read_values.setEnabled(not busy)
 | 
			
		||||
 | 
			
		||||
    def _on_serial_error(self, err):
 | 
			
		||||
        if err == QtSerialPort.QSerialPort.NoError:
 | 
			
		||||
            return
 | 
			
		||||
        self._log(f"[SERIAL ERR] {self.serial.errorString()} ({err})")
 | 
			
		||||
        # treat as txn failure if busy
 | 
			
		||||
        if self._busy:
 | 
			
		||||
            self._end_transaction()
 | 
			
		||||
    # ------------------------------------------------------------- Parsing ---
 | 
			
		||||
    def _parse_response(self, frame: bytes):
 | 
			
		||||
        # basic length check
 | 
			
		||||
        if len(frame) < 8:  # minimal structure
 | 
			
		||||
            self._log("[ERR] Frame too short")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # trailer: crcLo crcHi 0 0
 | 
			
		||||
        if len(frame) < 4:
 | 
			
		||||
            return  # can't parse yet
 | 
			
		||||
        crc_lo = frame[-4]
 | 
			
		||||
        crc_hi = frame[-3]
 | 
			
		||||
        crc_rx = (crc_hi << 8) | crc_lo
 | 
			
		||||
        z1 = frame[-2]
 | 
			
		||||
        z2 = frame[-1]
 | 
			
		||||
        if z1 != 0 or z2 != 0:
 | 
			
		||||
            self._log("[WARN] Frame trailer not 0,0")
 | 
			
		||||
 | 
			
		||||
        payload = frame[:-4]
 | 
			
		||||
        if self.auto_crc_check:
 | 
			
		||||
            crc_calc = crc16_ibm(payload)
 | 
			
		||||
            if crc_calc != crc_rx:
 | 
			
		||||
                self._log(f"[CRC FAIL] calc=0x{crc_calc:04X} rx=0x{crc_rx:04X}")
 | 
			
		||||
            else:
 | 
			
		||||
                self._log("[CRC OK]")
 | 
			
		||||
 | 
			
		||||
        # header fields
 | 
			
		||||
        addr = payload[0]
 | 
			
		||||
        cmd = payload[1]
 | 
			
		||||
        status = payload[2]
 | 
			
		||||
        iq = payload[3]
 | 
			
		||||
 | 
			
		||||
        if cmd != self.cmd_byte:
 | 
			
		||||
            self._log(f"[WARN] Unexpected cmd 0x{cmd:02X}")
 | 
			
		||||
 | 
			
		||||
        index = self._active_index if self._active_index is not None else self.spin_index.value()
 | 
			
		||||
 | 
			
		||||
        if self._waiting_name:
 | 
			
		||||
            name_bytes = payload[4:4 + self.name_field_len]
 | 
			
		||||
            # stop at first NUL
 | 
			
		||||
            nul = name_bytes.find(b"\x00")
 | 
			
		||||
            if nul >= 0:
 | 
			
		||||
                name_str = name_bytes[:nul].decode(errors="replace")
 | 
			
		||||
            else:
 | 
			
		||||
                name_str = name_bytes.decode(errors="replace")
 | 
			
		||||
            self.edit_name.setText(name_str)
 | 
			
		||||
            self.lbl_iq.setText(str(iq))
 | 
			
		||||
            self.nameRead.emit(index, status, iq, name_str)
 | 
			
		||||
        else:
 | 
			
		||||
            # Чтение значения
 | 
			
		||||
            if status != 0:
 | 
			
		||||
                # Ошибка чтения переменной — считаем её недействительной
 | 
			
		||||
                self.edit_value.setText("INVALID")
 | 
			
		||||
                self.edit_value.setStyleSheet("color: red; font-weight: bold;")
 | 
			
		||||
                self.lbl_iq.setText("-")
 | 
			
		||||
                self.valueRead.emit(index, status, iq, 0, float('nan'))
 | 
			
		||||
                self._log(f"[ERR] Variable at index {index} invalid, status={status}")
 | 
			
		||||
                return
 | 
			
		||||
        
 | 
			
		||||
            raw_lo = payload[4] if len(payload) > 4 else 0
 | 
			
		||||
            raw_hi = payload[5] if len(payload) > 5 else 0
 | 
			
		||||
            raw16 = (raw_hi << 8) | raw_lo
 | 
			
		||||
            if self.signed and (raw16 & 0x8000):
 | 
			
		||||
                raw_signed = raw16 - 0x10000
 | 
			
		||||
            else:
 | 
			
		||||
                raw_signed = raw16
 | 
			
		||||
            if self.chk_raw.isChecked():
 | 
			
		||||
                disp = str(raw_signed)
 | 
			
		||||
                float_val = float(raw_signed)
 | 
			
		||||
            else:
 | 
			
		||||
                scale = self.iq_scaling.get(iq, 1.0)
 | 
			
		||||
                float_val = raw_signed / scale
 | 
			
		||||
                disp = f"{float_val:.6g}"  # compact
 | 
			
		||||
            self.edit_value.setText(disp)
 | 
			
		||||
            self.lbl_iq.setText(str(iq))
 | 
			
		||||
            self.valueRead.emit(index, status, iq, raw_signed, float_val)
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------- Helpers ---
 | 
			
		||||
 | 
			
		||||
    def _toggle_index_base(self, state):
 | 
			
		||||
        val = self.spin_index.value()
 | 
			
		||||
        if state == QtCore.Qt.Checked:
 | 
			
		||||
            self.spin_index.setDisplayIntegerBase(16)
 | 
			
		||||
            self.spin_index.setPrefix("0x")
 | 
			
		||||
            self.spin_index.setValue(val)  # refresh display
 | 
			
		||||
        else:
 | 
			
		||||
            self.spin_index.setDisplayIntegerBase(10)
 | 
			
		||||
            self.spin_index.setPrefix("")
 | 
			
		||||
            self.spin_index.setValue(val)
 | 
			
		||||
 | 
			
		||||
    def _on_index_changed(self, new_index: int):
 | 
			
		||||
        if self._polling:
 | 
			
		||||
            self._index_change_timer.start(self._index_change_delay_ms)
 | 
			
		||||
 | 
			
		||||
    def _on_index_change_timeout(self):
 | 
			
		||||
        # Здесь запускаем запрос имени или значения по новому индексу
 | 
			
		||||
        if self._polling:
 | 
			
		||||
            # если включён polling — можно просто перезапустить опрос с новым индексом
 | 
			
		||||
            # например:
 | 
			
		||||
            self._restart_polling_cycle()
 | 
			
		||||
 | 
			
		||||
    def _restart_polling_cycle(self):
 | 
			
		||||
        # Прервать текущую транзакцию (если есть)
 | 
			
		||||
        if self._busy:
 | 
			
		||||
            # Если занято — запустить таймер, который через немного проверит снова
 | 
			
		||||
            # Можно, например, использовать QTimer.singleShot (если PyQt/PySide)
 | 
			
		||||
            QTimer.singleShot(10, self._restart_polling_cycle)  # через 100 мс повторить попытку
 | 
			
		||||
            return
 | 
			
		||||
        # можно отправить запрос имени, если нужно
 | 
			
		||||
        self.request_name()
 | 
			
		||||
        # Запустить следующий запрос
 | 
			
		||||
        self._on_poll_timeout()
 | 
			
		||||
 | 
			
		||||
    def _set_polling_ui(self, polling: bool):
 | 
			
		||||
        # Если polling == True -> блокируем кнопки Read/Write, иначе разблокируем
 | 
			
		||||
        self.btn_read_name.setDisabled(polling)
 | 
			
		||||
        self.btn_read_value.setDisabled(polling)
 | 
			
		||||
 | 
			
		||||
    def _toggle_polling(self):
 | 
			
		||||
        if self._polling:
 | 
			
		||||
            self._poll_timer.stop()
 | 
			
		||||
            self._polling = False
 | 
			
		||||
            self.btn_poll.setText("Start Polling")
 | 
			
		||||
            self._set_polling_ui(False)
 | 
			
		||||
            self._log("[POLL] Stopped")
 | 
			
		||||
        else:
 | 
			
		||||
            interval = self.spin_interval.value()
 | 
			
		||||
            self._poll_timer.start(interval)
 | 
			
		||||
            self._polling = True
 | 
			
		||||
            self.btn_poll.setText("Stop Polling")
 | 
			
		||||
            self._set_polling_ui(True)
 | 
			
		||||
            self._log(f"[POLL] Started, interval {interval} ms")
 | 
			
		||||
 | 
			
		||||
    def _on_poll_timeout(self):
 | 
			
		||||
        self._poll_once()
 | 
			
		||||
 | 
			
		||||
    def _poll_once(self):
 | 
			
		||||
        if self._polling and self.serial.isOpen() and not self._busy:
 | 
			
		||||
            self.request_value()
 | 
			
		||||
        # если busy -> просто пропускаем тик; не ставим pending, чтобы не накапливать очередь
 | 
			
		||||
 | 
			
		||||
    def _set_ui_busy(self, busy: bool):
 | 
			
		||||
        '''self.btn_read_name.setEnabled(not busy)
 | 
			
		||||
        self.btn_read_value.setEnabled(not busy)
 | 
			
		||||
        # Не запрещаем Stop Polling, иначе нельзя прервать зависший запрос
 | 
			
		||||
        self.spin_index.setEnabled(not busy)'''
 | 
			
		||||
            self._end_txn()
 | 
			
		||||
 | 
			
		||||
    # ------------------------------ LOGGING --------------------------------
 | 
			
		||||
    def _log(self, msg: str):
 | 
			
		||||
        ts = datetime.datetime.now().strftime("%H:%M:%S.%f")[:-3]
 | 
			
		||||
        ts = datetime.datetime.now().strftime('%H:%M:%S.%f')[:-3]
 | 
			
		||||
        self.txt_log.append(f"{ts} {msg}")
 | 
			
		||||
 | 
			
		||||
    def _log_frame(self, data: bytes, *, tx: bool):
 | 
			
		||||
        dir_tag = "TX" if tx else "RX"
 | 
			
		||||
        hex_bytes = ' '.join(f"{b:02X}" for b in data)
 | 
			
		||||
        # ascii printable map
 | 
			
		||||
        ascii_bytes = ''.join(chr(b) if 32 <= b < 127 else '.' for b in data)
 | 
			
		||||
        self._log(f"[{dir_tag}] {hex_bytes}  |{ascii_bytes}|")
 | 
			
		||||
 | 
			
		||||
        tag = 'TX' if tx else 'RX'
 | 
			
		||||
        hexs = ' '.join(f"{b:02X}" for b in data)
 | 
			
		||||
        ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in data)
 | 
			
		||||
        self._log(f"[{tag}] {hexs} |{ascii_part}|")
 | 
			
		||||
 | 
			
		||||
# ---------------------------------------------------------- Demo harness ---
 | 
			
		||||
class _DemoWindow(QtWidgets.QMainWindow):
 | 
			
		||||
@ -611,10 +711,18 @@ class _DemoWindow(QtWidgets.QMainWindow):
 | 
			
		||||
 | 
			
		||||
    def _on_value(self, index, status, iq, raw16, floatVal):
 | 
			
		||||
        print(f"Value idx={index} status={status} iq={iq} raw={raw16} val={floatVal}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ----------------------------------------------------------------- main ---
 | 
			
		||||
        
 | 
			
		||||
    def closeEvent(self, event):
 | 
			
		||||
        """Вызывается при закрытии окна."""
 | 
			
		||||
        # Явно удаляем центральный виджет
 | 
			
		||||
        self.setCentralWidget(None)
 | 
			
		||||
        if self.term:
 | 
			
		||||
            self.term.deleteLater()
 | 
			
		||||
            self.term = None
 | 
			
		||||
        super().closeEvent(event)
 | 
			
		||||
# ------------------------------- Demo --------------------------------------
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    import sys
 | 
			
		||||
    app = QtWidgets.QApplication(sys.argv)
 | 
			
		||||
    win = _DemoWindow()
 | 
			
		||||
    win.show()
 | 
			
		||||
 | 
			
		||||
@ -253,6 +253,8 @@ class VariableTableWidget(QTableWidget):
 | 
			
		||||
            ret_combo = CtrlScrollComboBox()
 | 
			
		||||
            ret_combo.addItems(self.iq_types)
 | 
			
		||||
            value = var['return_type'].replace('t_', '')
 | 
			
		||||
            if value not in self.iq_types:
 | 
			
		||||
                ret_combo.addItem(value)
 | 
			
		||||
            ret_combo.setCurrentText(value)
 | 
			
		||||
            ret_combo.currentTextChanged.connect(on_change_callback)
 | 
			
		||||
            ret_combo.setStyleSheet(style_with_padding)
 | 
			
		||||
@ -375,10 +377,9 @@ class VariableTableWidget(QTableWidget):
 | 
			
		||||
                    combo.clear()
 | 
			
		||||
                    
 | 
			
		||||
                    combo.addItems(allowed_items)
 | 
			
		||||
                    if current in allowed_items:
 | 
			
		||||
                        combo.setCurrentText(current)
 | 
			
		||||
                    else:
 | 
			
		||||
                        combo.setCurrentIndex(0)
 | 
			
		||||
                    if current not in allowed_items:
 | 
			
		||||
                        combo.addItem(current)
 | 
			
		||||
                    combo.setCurrentText(current)
 | 
			
		||||
                    combo.blockSignals(False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										109
									
								
								debug_tools.c
									
									
									
									
									
								
							
							
						
						
									
										109
									
								
								debug_tools.c
									
									
									
									
									
								
							@ -26,7 +26,7 @@ void Debug_Test_Example(void)
 | 
			
		||||
{
 | 
			
		||||
    return;
 | 
			
		||||
    result = Debug_ReadVar(var_numb, &return_var);
 | 
			
		||||
    result = Debug_ReadVarName(var_numb, var_name);
 | 
			
		||||
    result = Debug_ReadVarName(var_numb, var_name, 0);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if(Debug_LowLevel_Initialize(&ext_date) == 0)
 | 
			
		||||
@ -47,39 +47,76 @@ int Debug_ReadVar(int var_ind, int32_t *return_32b)
 | 
			
		||||
    int32_t tmp_var;
 | 
			
		||||
 | 
			
		||||
    if(return_32b == NULL)
 | 
			
		||||
        return 1;
 | 
			
		||||
        return DEBUG_ERR_INTERNAL;
 | 
			
		||||
    if (var_ind >= DebugVar_Qnt)
 | 
			
		||||
        return 1;
 | 
			
		||||
        return DEBUG_ERR_VAR_NUMB;
 | 
			
		||||
    if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) ||
 | 
			
		||||
            (dbg_vars[var_ind].ptr_type == pt_unknown))
 | 
			
		||||
        return 1;
 | 
			
		||||
        return DEBUG_ERR_INVALID_VAR;
 | 
			
		||||
 | 
			
		||||
    return convertDebugVarToIQx(&dbg_vars[var_ind], return_32b);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
  * @brief    Читает возвращаемый тип переменной по индексу.
 | 
			
		||||
  * @brief    ×èòàåò âîçâðàùàåìûé òèï (IQ) ïåðåìåííîé ïî èíäåêñó.
 | 
			
		||||
  * @param    var_ind      – èíäåêñ ïåðåìåííîé.
 | 
			
		||||
  * @param    vartype       – óêàçàòåëü äëÿ âîçâðàòà òèïà.
 | 
			
		||||
  * @return   int          – 0: óñïåõ, 1: îøèáêà.
 | 
			
		||||
  * @details  Используется для чтения значений переменных по их индексу.
 | 
			
		||||
  * @details  Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ âîçâðàùàåìîãî òèïà (IQ) ïåðåìåííûõ ïî èõ èíäåêñó.
 | 
			
		||||
  */
 | 
			
		||||
int Debug_ReadVarReturnType(int var_ind, int *vartype)
 | 
			
		||||
{
 | 
			
		||||
    int rettype;
 | 
			
		||||
    if(vartype == NULL)
 | 
			
		||||
        return 1;
 | 
			
		||||
        return DEBUG_ERR_INTERNAL;
 | 
			
		||||
    if (var_ind >= DebugVar_Qnt)
 | 
			
		||||
        return 1;
 | 
			
		||||
        return DEBUG_ERR_VAR_NUMB;
 | 
			
		||||
    if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) ||
 | 
			
		||||
            (dbg_vars[var_ind].ptr_type == pt_unknown))
 | 
			
		||||
        return 1;
 | 
			
		||||
        return DEBUG_ERR_INVALID_VAR;
 | 
			
		||||
 | 
			
		||||
    *vartype = iqTypeToQ(dbg_vars[var_ind].return_type);
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
  * @brief    ×èòàåò òèï ïåðåìåííîé ïî èíäåêñó.
 | 
			
		||||
  * @param    var_ind      – èíäåêñ ïåðåìåííîé.
 | 
			
		||||
  * @param    vartype      – óêàçàòåëü äëÿ âîçâðàòà òèïà.
 | 
			
		||||
  * @return   int          – 0: óñïåõ, 1: îøèáêà.
 | 
			
		||||
  * @details  Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ òèïà ïåðåìåííûõ ïî èõ èíäåêñó.
 | 
			
		||||
  */
 | 
			
		||||
int Debug_ReadVarType(int var_ind, int *vartype)
 | 
			
		||||
{
 | 
			
		||||
    int rettype;
 | 
			
		||||
    if(vartype == NULL)
 | 
			
		||||
        return DEBUG_ERR_INTERNAL;
 | 
			
		||||
    if (var_ind >= DebugVar_Qnt)
 | 
			
		||||
        return DEBUG_ERR_VAR_NUMB;
 | 
			
		||||
    if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) ||
 | 
			
		||||
            (dbg_vars[var_ind].ptr_type == pt_unknown))
 | 
			
		||||
        return DEBUG_ERR_INVALID_VAR;
 | 
			
		||||
 | 
			
		||||
    *vartype = dbg_vars[var_ind].ptr_type;
 | 
			
		||||
 | 
			
		||||
    switch(dbg_vars[var_ind].ptr_type)
 | 
			
		||||
    {
 | 
			
		||||
        case pt_int8:
 | 
			
		||||
        case pt_int16:
 | 
			
		||||
        case pt_int32:
 | 
			
		||||
        case pt_float:
 | 
			
		||||
            *vartype = dbg_vars[var_ind].ptr_type | DEBUG_SIGNED_VAR;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            *vartype = dbg_vars[var_ind].ptr_type;
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
  * @brief    ×èòàåò èìÿ ïåðåìåííîé ïî èíäåêñó.
 | 
			
		||||
  * @param    var_ind    – èíäåêñ ïåðåìåííîé.
 | 
			
		||||
@ -87,22 +124,26 @@ int Debug_ReadVarReturnType(int var_ind, int *vartype)
 | 
			
		||||
  * @return   int        – 0: óñïåõ, 1: îøèáêà.
 | 
			
		||||
  * @details  Êîïèðóåò èìÿ ïåðåìåííîé â ïðåäîñòàâëåííûé áóôåð.
 | 
			
		||||
  */
 | 
			
		||||
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr)
 | 
			
		||||
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr, int *length)
 | 
			
		||||
{
 | 
			
		||||
    int i;
 | 
			
		||||
 | 
			
		||||
    if(name_ptr == NULL)
 | 
			
		||||
        return 1;
 | 
			
		||||
        return DEBUG_ERR_INTERNAL;
 | 
			
		||||
 | 
			
		||||
    if (var_ind >= DebugVar_Qnt)
 | 
			
		||||
        return 1;
 | 
			
		||||
        return DEBUG_ERR_VAR_NUMB;
 | 
			
		||||
 | 
			
		||||
    // Êîïèðîâàíèå ñ çàùèòîé îò ïåðåïîëíåíèÿ è ÿâíîé îñòàíîâêîé ïî '\0'
 | 
			
		||||
    for (i = 0; i < sizeof(dbg_vars[var_ind].name); i++)
 | 
			
		||||
    {
 | 
			
		||||
        name_ptr[i] = dbg_vars[var_ind].name[i];
 | 
			
		||||
        if (dbg_vars[var_ind].name[i] == '\0')
 | 
			
		||||
        {
 | 
			
		||||
            if(length != NULL)
 | 
			
		||||
                *length = i;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // Ãàðàíòèðîâàííîå çàâåðøåíèå ñòðîêè (íà ñëó÷àé, åñëè â var->name íå áûëî '\0')
 | 
			
		||||
    name_ptr[sizeof(dbg_vars[var_ind].name) - 1] = '\0';
 | 
			
		||||
@ -123,9 +164,9 @@ int Debug_LowLevel_ReadVar(int32_t *return_32b)
 | 
			
		||||
    uint32_t addr_val = (uint32_t)addr;
 | 
			
		||||
 | 
			
		||||
    if (return_32b == NULL)
 | 
			
		||||
        return 1;
 | 
			
		||||
        return DEBUG_ERR_INTERNAL;
 | 
			
		||||
    if (debug_ll.isVerified == 0)
 | 
			
		||||
        return 1;
 | 
			
		||||
        return DEBUG_ERR_DATATIME;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // Ðàçðåø¸ííûå äèàïàçîíû ïàìÿòè (èç .cmd ôàéëà)
 | 
			
		||||
@ -139,7 +180,7 @@ int Debug_LowLevel_ReadVar(int32_t *return_32b)
 | 
			
		||||
        (addr_val >= 0x100002 && addr_val <= 0x103FFF) ||  // RAMEX0 + RAMEX2 + RAMEX01
 | 
			
		||||
        (addr_val >= 0x102000 && addr_val <= 0x103FFF)     // RAMEX2
 | 
			
		||||
    )) {
 | 
			
		||||
        return 2; // Запрещённый адрес — нельзя читать
 | 
			
		||||
        return DEBUG_ERR_ADDR; // Çàïðåù¸ííûé àäðåñ — íåëüçÿ ÷èòàòü
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return convertDebugVarToIQx(&debug_ll.dbg_var, return_32b);
 | 
			
		||||
@ -154,7 +195,7 @@ int Debug_LowLevel_ReadVar(int32_t *return_32b)
 | 
			
		||||
int Debug_LowLevel_Initialize(DateTime_t* external_date)
 | 
			
		||||
{
 | 
			
		||||
    if (external_date == NULL) {
 | 
			
		||||
        return -1;
 | 
			
		||||
        return DEBUG_ERR_INTERNAL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -170,7 +211,7 @@ int Debug_LowLevel_Initialize(DateTime_t* external_date)
 | 
			
		||||
    }
 | 
			
		||||
    debug_ll.isVerified = 0;
 | 
			
		||||
 | 
			
		||||
    return 1;  // Не совпало
 | 
			
		||||
    return DEBUG_ERR_DATATIME;  // Íå ñîâïàëî
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -193,7 +234,7 @@ static int iqTypeToQ(DebugVarIQType_t t)
 | 
			
		||||
    else if (t >= t_iq1 && t <= t_iq30)
 | 
			
		||||
        return (int)t - (int)t_iq1 + 1; // íàïðèìåð t_iq1 -> 1, t_iq2 -> 2 è ò.ä.
 | 
			
		||||
    else
 | 
			
		||||
        return -1;      // ошибка
 | 
			
		||||
        return 0;      // îøèáêà
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -208,19 +249,17 @@ static int convertDebugVarToIQx(DebugVar_t *var, int32_t *ret_var)
 | 
			
		||||
    int32_t iq_numb, iq_united, iq_final;
 | 
			
		||||
    int64_t iq_united64 = 0;
 | 
			
		||||
    int64_t iq_final64 = 0;
 | 
			
		||||
    int status;
 | 
			
		||||
 | 
			
		||||
    float float_numb;
 | 
			
		||||
 | 
			
		||||
    if(getDebugVar(var, &iq_numb, &float_numb) != 0)
 | 
			
		||||
        return 1;
 | 
			
		||||
    status = getDebugVar(var, &iq_numb, &float_numb);
 | 
			
		||||
    if(status != 0)
 | 
			
		||||
        return status;
 | 
			
		||||
 | 
			
		||||
    int src_q = iqTypeToQ(var->iq_type);
 | 
			
		||||
    int dst_q = iqTypeToQ(var->return_type);
 | 
			
		||||
 | 
			
		||||
    if (src_q < 0 || dst_q < 0)
 | 
			
		||||
        return 2; // неправильный формат
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // Êîíâåðòàöèÿ ê GLOBAL_Q (64-áèò)
 | 
			
		||||
    if (var->iq_type == t_iq_none) {
 | 
			
		||||
        if (var->ptr_type == pt_float) {
 | 
			
		||||
@ -249,10 +288,6 @@ static int convertDebugVarToIQx(DebugVar_t *var, int32_t *ret_var)
 | 
			
		||||
        else
 | 
			
		||||
            iq_final64 = iq_united64 >> (-shift);
 | 
			
		||||
 | 
			
		||||
//        // Проверяем переполнение int32_t
 | 
			
		||||
//        if (iq_final64 > 2147483647 || iq_final64 < -2147483648)
 | 
			
		||||
//            return 3; // переполнение
 | 
			
		||||
 | 
			
		||||
        *ret_var = (int32_t)iq_final64;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -273,51 +308,51 @@ static int getDebugVar(DebugVar_t *var, int32_t *int_var, float *float_var)
 | 
			
		||||
    uint32_t addr_val = (uint32_t)addr;
 | 
			
		||||
 | 
			
		||||
    if (!var || !int_var || !float_var || !var->Ptr)
 | 
			
		||||
        return 1; // ошибка: null указатель
 | 
			
		||||
        return DEBUG_ERR_INTERNAL; // îøèáêà: null óêàçàòåëü
 | 
			
		||||
 | 
			
		||||
    switch (var->ptr_type)
 | 
			
		||||
    {
 | 
			
		||||
        case pt_int8:      // 8 áèò
 | 
			
		||||
            if ((addr_val & ALIGN_8BIT) != 0)  // ïðîâåðÿåì âûðàâíèâàíèå
 | 
			
		||||
                return 1;   // ошибка выравнивания
 | 
			
		||||
                return DEBUG_ERR_ADDR_ALIGN;   // îøèáêà âûðàâíèâàíèÿ
 | 
			
		||||
            *int_var = *((volatile int8_t *)addr);
 | 
			
		||||
            break;
 | 
			
		||||
        case pt_uint8:
 | 
			
		||||
            if ((addr_val & ALIGN_8BIT) != 0)  // ïðîâåðÿåì âûðàâíèâàíèå
 | 
			
		||||
                return 1;   // ошибка выравнивания
 | 
			
		||||
                return DEBUG_ERR_ADDR_ALIGN;   // îøèáêà âûðàâíèâàíèÿ
 | 
			
		||||
            *int_var = *((volatile uint8_t *)addr);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case pt_int16:     // 16 áèò (int)
 | 
			
		||||
            if ((addr_val & ALIGN_16BIT) != 0)  // ïðîâåðÿåì âûðàâíèâàíèå
 | 
			
		||||
                return 2;   // ошибка выравнивания
 | 
			
		||||
                return DEBUG_ERR_ADDR_ALIGN;   // îøèáêà âûðàâíèâàíèÿ
 | 
			
		||||
            *int_var = *((volatile int16_t *)addr);
 | 
			
		||||
            break;
 | 
			
		||||
        case pt_uint16:
 | 
			
		||||
            if ((addr_val & ALIGN_16BIT) != 0)  // ïðîâåðÿåì âûðàâíèâàíèå
 | 
			
		||||
                return 2;   // ошибка выравнивания
 | 
			
		||||
                return DEBUG_ERR_ADDR_ALIGN;   // îøèáêà âûðàâíèâàíèÿ
 | 
			
		||||
            *int_var = *((volatile uint16_t *)addr);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case pt_int32:     // 32 áèò
 | 
			
		||||
            if ((addr_val & ALIGN_32BIT) != 0)  // ïðîâåðÿåì âûðàâíèâàíèå
 | 
			
		||||
                return 3;   // ошибка выравнивания
 | 
			
		||||
                return DEBUG_ERR_ADDR_ALIGN;   // îøèáêà âûðàâíèâàíèÿ
 | 
			
		||||
            *int_var = *((volatile int32_t *)addr);
 | 
			
		||||
            break;
 | 
			
		||||
        case pt_uint32:
 | 
			
		||||
            if ((addr_val & ALIGN_32BIT) != 0)  // ïðîâåðÿåì âûðàâíèâàíèå
 | 
			
		||||
                return 3;   // ошибка выравнивания
 | 
			
		||||
                return DEBUG_ERR_ADDR_ALIGN;   // îøèáêà âûðàâíèâàíèÿ
 | 
			
		||||
            *int_var = *((volatile uint32_t *)addr);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case pt_float:     // float (4 áàéòà)
 | 
			
		||||
            if ((addr_val & ALIGN_FLOAT) != 0)  // ïðîâåðêà âûðàâíèâàíèÿ
 | 
			
		||||
                return 4;   // ошибка выравнивания
 | 
			
		||||
                return DEBUG_ERR_ADDR_ALIGN;   // îøèáêà âûðàâíèâàíèÿ
 | 
			
		||||
            *float_var = *((volatile float *)addr);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            return 1; // неподдерживаемый тип
 | 
			
		||||
            return DEBUG_ERR_INVALID_VAR; // íåïîääåðæèâàåìûé òèï
 | 
			
		||||
        // äëÿ óêàçàòåëåé è ìàññèâîâ íå ïîääåðæèâàåòñÿ ÷òåíèå
 | 
			
		||||
//        case pt_ptr_int8:
 | 
			
		||||
//        case pt_ptr_int16:
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,18 @@
 | 
			
		||||
    #define NULL 0
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define DEBUG_SIGNED_VAR                (1<<7)
 | 
			
		||||
 | 
			
		||||
#define DEBUG_OK                        (0)
 | 
			
		||||
#define DEBUG_ERR                       (1<<7)
 | 
			
		||||
#define DEBUG_ERR_VAR_NUMB              (1<<0) | DEBUG_ERR
 | 
			
		||||
#define DEBUG_ERR_INVALID_VAR           (1<<1) | DEBUG_ERR
 | 
			
		||||
#define DEBUG_ERR_ADDR                  (1<<2) | DEBUG_ERR
 | 
			
		||||
#define DEBUG_ERR_ADDR_ALIGN            (1<<3) | DEBUG_ERR
 | 
			
		||||
#define DEBUG_ERR_INTERNAL              (1<<4) | DEBUG_ERR
 | 
			
		||||
#define DEBUG_ERR_DATATIME              (1<<5) | DEBUG_ERR
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
  * @brief Òèï äàííûõ, íà êîòîðûé óêàçûâàåò óêàçàòåëü ïåðåìåííîé îòëàäêè.
 | 
			
		||||
@ -159,9 +171,11 @@ void Debug_Test_Example(void);
 | 
			
		||||
/* ×èòàåò çíà÷åíèå ïåðåìåííîé ïî èíäåêñó */
 | 
			
		||||
int Debug_ReadVar(int var_ind, int32_t *return_long);
 | 
			
		||||
/* ×èòàåò èìÿ ïåðåìåííîé ïî èíäåêñó */
 | 
			
		||||
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr);
 | 
			
		||||
/* ×èòàåò âîçâðàùàåìûé òèï ïåðåìåííîé ïî èíäåêñó */
 | 
			
		||||
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr, int *length);
 | 
			
		||||
/* ×èòàåò âîçâðàùàåìûé òèï (IQ) ïåðåìåííîé ïî èíäåêñó */
 | 
			
		||||
int Debug_ReadVarReturnType(int var_ind, int *vartype);
 | 
			
		||||
/* ×èòàåò òèï ïåðåìåííîé ïî èíäåêñó */
 | 
			
		||||
int Debug_ReadVarType(int var_ind, int *vartype);
 | 
			
		||||
/* ×èòàåò çíà÷åíèå ïåðåìåííîé ñ íèæíåãî óðîâíÿ */
 | 
			
		||||
int Debug_LowLevel_ReadVar(int32_t *return_long);
 | 
			
		||||
/* Èíèöèàëèçèðóåò îòëàäêó íèæíåãî óðîâíÿ */
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user