Compare commits

...

14 Commits
v1.0 ... master

Author SHA1 Message Date
e99de603e6 попытка сделать в parse_xml парсинг вложенных массивов [][]
определяет вложенные массивы но не определяет их размерности (нули)
2025-07-22 18:57:59 +03:00
788ad19464 кууууча всего по терминалке, надо резгребать и структурировать
базово:
+сделан lowlevel для кучи переменных (пока работает медленно)
+сделан сохранение принимаемых значений в лог
+ gui терминалок подогнаны под один стиль плюс минус
2025-07-22 18:05:12 +03:00
96496a0256 все неплохо работает.
сейв перед попыткой улучшить lowlevel debug
2025-07-21 13:40:52 +03:00
f89aff1b1c Улучшена скорость полла Watch (переделано формирование таблицы) 2025-07-19 18:17:00 +03:00
6830743477 Начата работа над lowlevel терминалкой (по адресам из xml) 2025-07-19 18:01:40 +03:00
171f176d63 добавлен exe для парса всех переменных из .out 2025-07-19 11:36:32 +03:00
f2c4b7b3cd структурирован код debug_tools
доработана демо-терминалка для считывания tms переменных и встроена в DebugVarEdit
2025-07-19 10:56:46 +03:00
c94a7e711c сделана бета терминалка для опроса переменных 2025-07-18 17:43:23 +03:00
5be6343c33 бета терминалка для опроса 2025-07-18 16:47:18 +03:00
043359fe66 + фикс кривых проверок на наличие uint8_t
+ фикс перевод float в iq
+ фикс поиска вручнуб добавленных переменных в debug_vars.c
2025-07-17 10:37:58 +03:00
c55f38ef1c + пример debug_vars.c
+ exe
+ опечатка в readme
+ коррекции по абзацам в комментах
2025-07-17 09:26:57 +03:00
ae2c90160e +библиотека .c/.h переписана под универсальные дефайны intX_t uintX_t
+сделано задание размера короткого имени
+ добавлена бета поддержка stm:+
   - парс переменных из файла преокта Keil или makefile CubeIDE
   - запись в utf-8 для STM, вместо cp1251 для TMS
   - другой размер int (32 бита, вместо 16 бит) для STM
2025-07-17 09:18:03 +03:00
4de53090a1 добавлены комменты к debug_tools.c/.h
начата работа над поддержкой stm32 и кейл проектов
2025-07-15 19:05:52 +03:00
742c4e9e1b readme для .c файлов 2025-07-15 15:56:47 +03:00
20 changed files with 4439 additions and 972 deletions

3
.gitignore vendored
View File

@ -4,4 +4,5 @@
/build_temp /build_temp
/DebugVarEdit_GUI.build /DebugVarEdit_GUI.build
/DebugVarEdit_GUI.dist /DebugVarEdit_GUI.dist
/DebugVarEdit_GUI.onefile-build /DebugVarEdit_GUI.onefile-build
/parse_xml/build/

BIN
DebugTools.rar Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,23 +1,56 @@
# DebugVarEdit — Утилита для генерации переменных для отладки # DebugTools - Просмотр переменных по указателям
Модуль состоит из трех файлов:
- **debug_tools.c** - реализация считывания переменных
- **debug_tools.h** - объявление всякого для считывания переменных
- **debug_vars.c** - определение массива считываемых переменных
Этот модуль предоставляет функциональность для чтения значений переменных во встроенной системе, включая работу с IQ-форматами, защиту доступа и проверку диапазонов памяти.
Для чтения переменных можно использовать функции:
```c
int Debug_ReadVar(int var_ind, int32_t *return_long);
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr);
```
Переменные доступные для чтения определяются в **debug_vars.c** (их можно прописывать вручную или генерировать через **DebugVarEdit**):
```c
// Определение массива с указателями на переменные для отладки
int DebugVar_Qnt = 5;
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
// pointer_type iq_type return_iq_type short_name
DebugVar_t dbg_vars[] = {\
{(uint8_t *)&freqTerm, pt_float, t_iq_none, t_iq10, "freqT" }, \
{(uint8_t *)&ADC_sf[0][0], pt_int16, t_iq_none, t_iq_none, "ADC_sf00" }, \
{(uint8_t *)&ADC_sf[0][1], pt_int16, t_iq_none, t_iq_none, "ADC_sf01" }, \
{(uint8_t *)&ADC_sf[0][2], pt_int16, t_iq_none, t_iq_none, "ADC_sf02" }, \
{(uint8_t *)&ADC_sf[0][3], pt_int16, t_iq_none, t_iq_none, "ADC_sf03" }, \
{(uint8_t *)&Bender[0].KOhms, pt_uint16, t_iq, t_iq10, "Bend0.KOhm" }, \
{(uint8_t *)&Bender[0].Times, pt_uint16, t_iq_none, t_iq_none, "Bend0.Time" }, \
};
```
# DebugVarEdit - Настройка переменных
**DebugVarEdit** — графическое приложение для Windows, предназначенное для настройки и генерации отладочных переменных (`debug_vars.c`) на основе исходного C-проекта. Работает с `makefile` проекта, сохраняет изменения в XML и позволяет удобно редактировать переменные и их типы через интерфейс. **DebugVarEdit** — графическое приложение для Windows, предназначенное для настройки и генерации отладочных переменных (`debug_vars.c`) на основе исходного C-проекта. Работает с `makefile` проекта, сохраняет изменения в XML и позволяет удобно редактировать переменные и их типы через интерфейс.
Программа — один исполняемый файл `DebugVarEdit.exe`, не требующий установки и дополнительных зависимостей. Программа — один исполняемый файл `DebugVarEdit.exe`, не требующий установки и дополнительных зависимостей.
> Требуется Windows 7 или новее. > Требуется Windows 7 или новее.
> [Для разработчиков](#для-разработчиков)
--- ---
## Как использовать ## Как использовать приложение
1. Запустите **DebugVarEdit.exe.** 1. Запустите **DebugVarEdit.exe.**
2. Укажите пути: 2. Укажите пути и контроллер:
- **Путь к XML** — новый или существующий файл настроек переменных; - **Путь к XML** — новый или существующий файл настроек переменных;
- **Путь к проекту** — путь к корню проека (папка, которая является корнем для проекта в Code Composer); - **Путь к проекту** — путь к корню проека (папка, которая является корнем для проекта в Code Composer);
- **Путь к makefile** — путь к `makefile` относительно корня проекта; - **Путь к makefile** — путь к `makefile` относительно корня проекта;
- **Путь для debug_vars.c** — папка, куда будет сгенерирован файл `debug_vars.c` с переменными. - **Путь для debug_vars.c** — папка, куда будет сгенерирован файл `debug_vars.c` с переменными.
- **МК** — TMS или STM. Они отличаются размером int, и также принятыми кодировками файлов (utf-8/cp1251)
3. Нажмите **Сканировать переменные**: 3. Нажмите **Сканировать переменные**:
- Программа проанализирует исходники, указанные в makefile, и найдёт все переменные. - Программа проанализирует исходники, указанные в makefile, и найдёт все переменные.
@ -61,7 +94,7 @@
## Пример XML-файла ## Пример XML-файла
```xml ```xml
<project proj_path="C:/myproj" makefile_path="Debug/Makefile" structs_path="debugVars/structs.xml"> <project proj_path="C:/myproj" makefile_path="Debug/Makefile" structs_path="Src/DebugTools/structs.xml">
<variables> <variables>
<var name="g_myvar"> <var name="g_myvar">
<enable>true</enable> <enable>true</enable>
@ -79,6 +112,7 @@
</project> </project>
``` ```
---
--- ---
# Для разработчиков # Для разработчиков
@ -107,14 +141,14 @@ Src
Для запуска приложения: Для запуска приложения:
- **Python 3.7+** - **Python 3.7+**
- **clang** — используется для парсинга C-кода (требуется `libclang.dll`) - **clang** — используется для парсинга C-кода (требуется `libclang.dll`, лежит в папке Src)
- **PySide2** — GUI-фреймворк - **PySide2** — GUI-фреймворк
- **lxml** — работа с XML - **lxml** — работа с XML
> Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7 > Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7
Для сборки `.exe`: Для сборки `.exe`:
- **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей - **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей
- **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен) - **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен для запуска `.exe`)
### Сборка: ### Сборка:
@ -134,5 +168,5 @@ Src
### Установка зависимостей ### Установка зависимостей
```bash ```bash
pip install PySide2 lxml nuitka pyinstaller pip install clang PySide2 lxml nuitka pyinstaller
``` ```

View File

@ -5,8 +5,9 @@ import sys
import os import os
import subprocess import subprocess
import lxml.etree as ET import lxml.etree as ET
from generate_debug_vars import type_map from generate_debug_vars import type_map, choose_type_map
from enum import IntEnum from enum import IntEnum
from tms_debugvar_term import _DemoWindow
import threading import threading
from generate_debug_vars import run_generate from generate_debug_vars import run_generate
import var_setup import var_setup
@ -17,12 +18,14 @@ import scan_vars
import myXML import myXML
import time import time
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem, QApplication, QWidget, QTableWidget, QTableWidgetItem,
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton, QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit, QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView,
) QMenuBar, QMenu, QAction
)
from PySide2.QtGui import QTextCursor, QKeyEvent, QIcon, QFont from PySide2.QtGui import QTextCursor, QKeyEvent, QIcon, QFont
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QSettings from PySide2.QtCore import Qt, QProcess, QObject, Signal, QSettings
@ -58,6 +61,7 @@ class VarEditor(QWidget):
self.output_path = None self.output_path = None
self._updating = False # Флаг блокировки рекурсии self._updating = False # Флаг блокировки рекурсии
self._resizing = False # флаг блокировки повторного вызова self._resizing = False # флаг блокировки повторного вызова
self.target = 'TMS'
self.initUI() self.initUI()
def initUI(self): def initUI(self):
@ -122,6 +126,43 @@ class VarEditor(QWidget):
self.btn_update_vars = QPushButton(scan_title) self.btn_update_vars = QPushButton(scan_title)
self.btn_update_vars.clicked.connect(self.update_vars_data) self.btn_update_vars.clicked.connect(self.update_vars_data)
# Добавляем чекбокс для выбора типовой карты
# --- Создаем верхнее меню ---
menubar = QMenuBar(self)
menubar.setToolTip('Разные размеры int и кодировки файлов')
self.target_menu = QMenu("МК:", menubar)
# Создаем действия для выбора Target
self.action_tms = QAction("TMS", self, checkable=True)
self.action_stm = QAction("STM", self, checkable=True)
# Инициализируем QSettings с именем организации и приложения
self.settings = QSettings("SET", "DebugVarEdit_MainWindow")
# Восстанавливаем сохранённое состояние, если есть
mcu = self.settings.value("mcu_choosen", True, type=str)
self.on_target_selected(mcu)
self.target_menu.setToolTip(f'TMS: Размер int 16 бит. Кодировка cp1251\nSTM: Размер int 32 бита. Кодировка utf-8')
# Группируем действия чтобы выбирался только один
self.action_tms.triggered.connect(lambda: self.on_target_selected("TMS"))
self.action_tms.setToolTip('Размер int 16 бит. Кодировка cp1251')
self.action_stm.triggered.connect(lambda: self.on_target_selected("STM"))
self.action_stm.setToolTip('Размер int 32 бита. Кодировка utf-8')
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) btn_save = QPushButton(build_title)
btn_save.clicked.connect(self.save_build) btn_save.clicked.connect(self.save_build)
@ -137,6 +178,7 @@ class VarEditor(QWidget):
self.table = VariableTableWidget() self.table = VariableTableWidget()
# Основной layout # Основной layout
layout = QVBoxLayout() layout = QVBoxLayout()
layout.setMenuBar(menubar) # прикрепляем menubar в layout сверху
layout.addLayout(xml_layout) layout.addLayout(xml_layout)
layout.addLayout(proj_layout) layout.addLayout(proj_layout)
layout.addLayout(makefile_layout) layout.addLayout(makefile_layout)
@ -149,8 +191,30 @@ class VarEditor(QWidget):
self.setLayout(layout) 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)
self.target = target.lower()
if self.target == "stm":
choose_type_map(True)
self.action_stm.setChecked(True)
self.action_tms.setChecked(False)
else:
choose_type_map(False)
self.action_tms.setChecked(True)
self.action_stm.setChecked(False)
def get_xml_path(self): def get_xml_path(self):
xml_path = self.xml_output_edit.text().strip() xml_path = self.xml_output_edit.text().strip()
return xml_path return xml_path
@ -241,7 +305,7 @@ class VarEditor(QWidget):
return return
try: try:
run_generate(self.proj_path, self.xml_path, self.output_path) run_generate(self.proj_path, self.xml_path, self.output_path, self.table._shortname_size)
QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.") QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.")
self.update() self.update()
except Exception as e: except Exception as e:
@ -358,15 +422,25 @@ class VarEditor(QWidget):
super().keyPressEvent(event) super().keyPressEvent(event)
def __browse_makefile(self): def __browse_makefile(self):
if self.target == 'stm':
file_filter = "Makefile или Keil-проект (*.uvprojx *.uvproj makefile);;Все файлы (*)"
dialog_title = "Выберите Makefile или Keil-проект"
else: # 'TMS' или по умолчанию
file_filter = "Makefile (makefile);;Все файлы (*)"
dialog_title = "Выберите Makefile"
file_path, _ = QFileDialog.getOpenFileName( file_path, _ = QFileDialog.getOpenFileName(
self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)" self,
dialog_title,
filter=file_filter
) )
if file_path and self.proj_path: if file_path:
path = myXML.make_relative_path(file_path, self.proj_path) if self.proj_path:
else: path = myXML.make_relative_path(file_path, self.proj_path)
path = file_path else:
self.makefile_edit.setText(path) path = file_path
self.makefile_path = path self.makefile_edit.setText(path)
self.makefile_path = path
def __browse_source_output(self): def __browse_source_output(self):
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c") dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c")
@ -438,12 +512,12 @@ class VarEditor(QWidget):
self.write_to_xml() self.write_to_xml()
def __open_variable_selector(self): def __open_variable_selector(self):
self.update()
if not self.vars_list: if not self.vars_list:
QMessageBox.warning(self, "Нет переменных", f"Сначала загрузите переменные ({scan_title}).") QMessageBox.warning(self, "Нет переменных", f"Сначала загрузите переменные ({scan_title}).")
return return
self.update()
dlg = VariableSelectorDialog(self.table, self.vars_list, self.structs, self.typedef_map, self.xml_path, self) dlg = VariableSelectorDialog(self.table, self.vars_list, self.structs, self.typedef_map, self.xml_path, self)
if dlg.exec_(): if dlg.exec_():
self.write_to_xml() self.write_to_xml()

438
Src/allvars_xml_parser.py Normal file
View File

@ -0,0 +1,438 @@
from __future__ import annotations
import sys
import re
import xml.etree.ElementTree as ET
import var_setup
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple
from PySide2.QtWidgets import (
QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton,
QLineEdit, QLabel, QHeaderView, QCompleter, QCheckBox, QHBoxLayout, QSizePolicy,
QTableWidget, QTableWidgetItem, QFileDialog, QWidget, QMessageBox, QApplication, QMainWindow
)
from PySide2 import QtCore, QtGui
from path_hints import PathHints
from generate_debug_vars import choose_type_map, type_map
from var_selector_window import VariableSelectorDialog
from typing import List, Tuple, Optional, Dict, Any, Set
DATE_FIELD_SET = {'year','month','day','hour','minute'}
@dataclass
class MemberNode:
name: str
offset: int = 0
type_str: str = ''
size: Optional[int] = None
children: List['MemberNode'] = field(default_factory=list)
# --- новые, но необязательные (совместимость) ---
kind: Optional[str] = None # 'array', 'union', ...
count: Optional[int] = None # size1 (число элементов в массиве)
def is_date_struct(self) -> bool:
if not self.children:
return False
child_names = {c.name for c in self.children}
return DATE_FIELD_SET.issubset(child_names)
@dataclass
class VariableNode:
name: str
address: int
type_str: str
size: Optional[int]
members: List[MemberNode] = field(default_factory=list)
# --- новые, но необязательные ---
kind: Optional[str] = None # 'array'
count: Optional[int] = None # size1
def base_address_hex(self) -> str:
return f"0x{self.address:06X}"
# --------------------------- XML Parser ----------------------------
class VariablesXML:
"""
Reads your XML and outputs a flat list of paths:
- Arrays -> name[i], multilevel -> name[i][j]
- Pointer to struct -> children via '->'
- Regular struct -> children via '.'
"""
# assumed primitive sizes (for STM/MCU: int=2)
_PRIM_SIZE = {
'char':1, 'signed char':1, 'unsigned char':1, 'uint8_t':1, 'int8_t':1,
'short':2, 'short int':2, 'signed short':2, 'unsigned short':2,
'uint16_t':2, 'int16_t':2,
'int':2, 'signed int':2, 'unsigned int':2,
'long':4, 'unsigned long':4, 'int32_t':4, 'uint32_t':4,
'float':4,
'long long':8, 'unsigned long long':8, 'int64_t':8, 'uint64_t':8, 'double':8,
}
def __init__(self, path: str):
self.path = path
self.timestamp: str = ''
self.variables: List[VariableNode] = []
choose_type_map(0)
self._parse()
# ------------------ low helpers ------------------
@staticmethod
def _parse_int_guess(txt: Optional[str]) -> Optional[int]:
if not txt:
return None
txt = txt.strip()
if txt.startswith(('0x','0X')):
return int(txt, 16)
# если в строке есть буквы A-F → возможно hex
if any(c in 'abcdefABCDEF' for c in txt):
try:
return int(txt, 16)
except ValueError:
pass
try:
return int(txt, 10)
except ValueError:
return None
@staticmethod
def _is_pointer_to_struct(t: str) -> bool:
if not t:
return False
low = t.replace('\t',' ').replace('\n',' ')
return 'struct ' in low and '*' in low
@staticmethod
def _is_struct_or_union(t: str) -> bool:
if not t:
return False
low = t.strip()
return low.startswith('struct ') or low.startswith('union ')
@staticmethod
def _strip_array_suffix(t: str) -> str:
return t[:-2].strip() if t.endswith('[]') else t
def _guess_primitive_size(self, type_str: str) -> Optional[int]:
if not type_str:
return None
base = type_str
for tok in ('volatile','const'):
base = base.replace(tok, '')
base = base.replace('*',' ')
base = base.replace('[',' ').replace(']',' ')
base = ' '.join(base.split()).strip()
return self._PRIM_SIZE.get(base)
# ------------------ XML read ------------------
def _parse(self):
try:
tree = ET.parse(self.path)
root = tree.getroot()
ts = root.find('timestamp')
self.timestamp = ts.text.strip() if ts is not None and ts.text else ''
def parse_member(elem) -> MemberNode:
name = elem.get('name','')
offset = int(elem.get('offset','0'),16) if elem.get('offset') else 0
t = elem.get('type','') or ''
size_attr = elem.get('size')
size = int(size_attr,16) if size_attr else None
kind = elem.get('kind')
size1_attr = elem.get('size1')
count = None
if size1_attr:
count = self._parse_int_guess(size1_attr)
node = MemberNode(name=name, offset=offset, type_str=t, size=size,
kind=kind, count=count)
for ch in elem.findall('member'):
node.children.append(parse_member(ch))
return node
for var in root.findall('variable'):
addr = int(var.get('address','0'),16)
name = var.get('name','')
t = var.get('type','') or ''
size_attr = var.get('size')
size = int(size_attr,16) if size_attr else None
kind = var.get('kind')
size1_attr = var.get('size1')
count = None
if size1_attr:
count = self._parse_int_guess(size1_attr)
members = [parse_member(m) for m in var.findall('member')]
self.variables.append(
VariableNode(name=name, address=addr, type_str=t, size=size,
members=members, kind=kind, count=count)
)
except FileNotFoundError:
self.variables = []
except ET.ParseError:
self.variables = []
# ------------------ flatten (expanded) ------------------
def flattened(self,
max_array_elems: Optional[int] = None
) -> List[Dict[str, Any]]:
"""
Returns a list of dictionaries with full data for variables and their expanded members.
Each dictionary contains: 'name', 'address', 'type', 'size', 'kind', 'count'.
max_array_elems: limit unfolding of large arrays (None = all).
"""
out: List[Dict[str, Any]] = []
def get_dict(name: str, address: int, type_str: str, size: Optional[int], kind: Optional[str], count: Optional[int]) -> Dict[str, Any]:
"""Helper to create the output dictionary format."""
return {
'name': name,
'address': address,
'type': type_str,
'size': size,
'kind': kind,
'count': count
}
def compute_stride(size_bytes: Optional[int],
count: Optional[int],
base_type: Optional[str],
node_children: Optional[List[MemberNode]]) -> int:
"""Calculates the stride (size of one element) for arrays."""
# 1) size_bytes/count
if size_bytes and count and count > 0:
if size_bytes % count == 0:
stride = size_bytes // count
if stride <= 0:
stride = 1
return stride
else:
# size not divisible by count → most likely size = size of one element
return max(size_bytes, 1)
# 2) attempt by type (primitive)
if base_type:
gs = self._guess_primitive_size(base_type)
if gs:
return gs
# 3) attempt by children (structure)
if node_children:
if not node_children:
return 1
min_off = min(ch.offset for ch in node_children)
max_end = min_off
for ch in node_children:
sz = ch.size
if not sz:
sz = self._guess_primitive_size(ch.type_str) or 1
end = ch.offset + sz
if end > max_end:
max_end = end
stride = max_end - min_off
if stride > 0:
return stride
return 1
def expand_members(prefix_name: str,
base_addr: int,
members: List[MemberNode],
parent_is_ptr_struct: bool):
"""
Recursively expands members of structs/unions or pointed-to structs.
parent_is_ptr_struct: if True, connection is '->' otherwise '.'
"""
join = '->' if parent_is_ptr_struct else '.'
for m in members:
path_m = f"{prefix_name}{join}{m.name}" if prefix_name else m.name
addr_m = base_addr + m.offset
out.append(get_dict(path_m, addr_m, m.type_str, m.size, m.kind, m.count))
# array?
if (m.kind == 'array') or m.type_str.endswith('[]'):
count = m.count
if count is None:
count = 0
if count <= 0:
continue
base_t = self._strip_array_suffix(m.type_str)
stride = compute_stride(m.size, count, base_t, m.children if m.children else None)
limit = count if max_array_elems is None else min(count, max_array_elems)
for i in range(limit):
path_i = f"{path_m}[{i}]"
addr_i = addr_m + i*stride
# Determine kind for array element based on its base type
elem_kind = None
if self._is_struct_or_union(base_t):
elem_kind = 'struct' # or 'union' depending on `base_t` prefix
elif self._guess_primitive_size(base_t):
elem_kind = 'primitive'
# For array elements, 'size' is the stride (size of one element), 'count' is None.
out.append(get_dict(path_i, addr_i, base_t, stride, elem_kind, None))
# array element: if structure / union → unfold fields
if m.children and self._is_struct_or_union(base_t):
expand_members(path_i, addr_i, m.children, parent_is_ptr_struct=False)
# array element: if pointer to structure
elif self._is_pointer_to_struct(base_t):
# usually no children in XML for these, but if present — use them
expand_members(path_i, addr_i, m.children, parent_is_ptr_struct=True)
continue
# not an array, but has children (e.g., struct/union)
if m.children:
is_ptr_struct = self._is_pointer_to_struct(m.type_str)
expand_members(path_m, addr_m, m.children, parent_is_ptr_struct=is_ptr_struct)
# --- top-level variables ---
for v in self.variables:
out.append(get_dict(v.name, v.address, v.type_str, v.size, v.kind, v.count))
# top-level array?
if (v.kind == 'array') or v.type_str.endswith('[]'):
count = v.count
if count is None:
count = 0
if count > 0:
base_t = self._strip_array_suffix(v.type_str)
stride = compute_stride(v.size, count, base_t, v.members if v.members else None)
limit = count if max_array_elems is None else min(count, max_array_elems)
for i in range(limit):
p = f"{v.name}[{i}]"
a = v.address + i*stride
# Determine kind for array element
elem_kind = None
if self._is_struct_or_union(base_t):
elem_kind = 'struct' # or 'union'
elif self._guess_primitive_size(base_t):
elem_kind = 'primitive'
out.append(get_dict(p, a, base_t, stride, elem_kind, None))
# array of structs?
if v.members and self._is_struct_or_union(base_t):
expand_members(p, a, v.members, parent_is_ptr_struct=False)
# array of pointers to structs?
elif self._is_pointer_to_struct(base_t):
expand_members(p, a, v.members, parent_is_ptr_struct=True)
continue
# top-level not an array, but has members
if v.members:
is_ptr_struct = self._is_pointer_to_struct(v.type_str)
expand_members(v.name, v.address, v.members, parent_is_ptr_struct=is_ptr_struct)
return out
# -------------------- date candidates (as it was) --------------------
def date_struct_candidates(self) -> List[Tuple[str,int]]:
cands = []
for v in self.variables:
# top level (if all date fields are present)
direct_names = {mm.name for mm in v.members}
if DATE_FIELD_SET.issubset(direct_names):
cands.append((v.name, v.address))
# check first-level members
for m in v.members:
if m.is_date_struct():
cands.append((f"{v.name}.{m.name}", v.address + m.offset))
return cands
def get_all_vars_data(self) -> List[Dict[str, Any]]:
"""
Возвращает вложенную структуру словарей с полными данными для всех переменных и их развернутых членов.
Каждый словарь представляет узел в иерархии и содержит:
'name' (полный путь), 'address', 'size', 'type', 'kind', 'count', и 'children' (если есть).
Логика определения родительского пути теперь использует `split_path` для анализа структуры пути.
"""
flat_data = self.flattened(max_array_elems=None)
root_nodes: List[Dict[str, Any]] = []
all_nodes_map: Dict[str, Dict[str, Any]] = {}
for item in flat_data:
node_dict = {**item, 'children': []}
all_nodes_map[item['name']] = node_dict
# Вспомогательная функция для определения полного пути родителя с использованием split_path
def get_parent_path_using_split(full_path: str) -> Optional[str]:
# 1. Используем split_path для получения компонентов пути.
components = var_setup.split_path(full_path)
# Если нет компонентов или только один (верхний уровень, не массивный элемент)
if not components or len(components) == 1:
# Если компонент один и это не индекс массива (например, "project" или "my_var")
# тогда у него нет родителя в этой иерархии.
# Если это был бы "my_array[0]" -> components=['my_array', '[0]'], len=2
if len(components) == 1 and not components[0].startswith('['):
return None
elif len(components) == 2 and components[-1].startswith('['): # like "my_array[0]"
return components[0] # Return "my_array" as parent
else: # Edge cases or malformed, treat as root
return None
# 2. Определяем, как отрезать "хвост" из оригинальной строки `full_path`, чтобы получить родителя.
# Эта логика остаётся похожей на предыдущую, так как `split_path` не включает разделители
# и мы должны получить точную строку родительского пути.
# Находим индекс последнего разделителя '.' или '->'
last_dot_idx = full_path.rfind('.')
last_arrow_idx = full_path.rfind('->')
effective_last_sep_idx = -1
if last_dot_idx > last_arrow_idx:
effective_last_sep_idx = last_dot_idx
elif last_arrow_idx != -1:
effective_last_sep_idx = last_arrow_idx
# Находим начало последнего суффикса массива (e.g., '[0]') в оригинальной строке
array_suffix_match = re.search(r'(\[[^\]]*\])+$', full_path)
array_suffix_start_idx = -1
if array_suffix_match:
array_suffix_start_idx = array_suffix_match.start()
# Логика определения родителя:
# - Если есть суффикс массива, и он находится после последнего разделителя (или разделителей нет),
# то родитель - это часть до суффикса массива. (e.g., 'project.adc[0]' -> 'project.adc')
# - Иначе, если есть разделитель, родитель - это часть до последнего разделителя. (e.g., 'project.adc.bus' -> 'project.adc')
# - Иначе (ни разделителей, ни суффиксов), это корневой элемент.
if array_suffix_start_idx != -1 and (array_suffix_start_idx > effective_last_sep_idx):
return full_path[:array_suffix_start_idx]
elif effective_last_sep_idx != -1:
return full_path[:effective_last_sep_idx]
else:
return None # Корневой элемент без явного родителя
# Основная логика get_all_vars_data
# Заполнение связей "родитель-потомок"
for item_name, node_dict in all_nodes_map.items():
parent_name = get_parent_path_using_split(item_name) # Используем новую вспомогательную функцию
if parent_name and parent_name in all_nodes_map:
all_nodes_map[parent_name]['children'].append(node_dict)
else:
root_nodes.append(node_dict)
# Сортируем корневые узлы и их детей рекурсивно по имени
def sort_nodes(nodes_list: List[Dict[str, Any]]):
nodes_list.sort(key=lambda x: x['name'])
for node in nodes_list:
if node['children']:
sort_nodes(node['children'])
sort_nodes(root_nodes)
return root_nodes

223
Src/csv_logger.py Normal file
View File

@ -0,0 +1,223 @@
import csv
import numbers
import time
from datetime import datetime
from PySide2 import QtWidgets
class CsvLogger:
"""
Логгер, совместимый по формату с C-реализацией CSV_AddTitlesLine / CSV_AddLogLine.
Публичный API сохранён:
set_titles(varnames)
set_value(timestamp, varname, varvalue)
select_file(parent=None) -> bool
write_to_csv()
Использование:
1) set_titles([...])
2) многократно set_value(ts, name, value)
3) select_file() (по желанию)
4) write_to_csv()
"""
def __init__(self, filename="log.csv", delimiter=';'):
self._filename = filename
self._delimiter = delimiter
# Пользовательские заголовки
self.variable_names_ordered = []
# Полные заголовки CSV (Ticks(X), Ticks(Y), Time(Y), ...)
self.headers = ['t'] # до вызова set_titles placeholder
# Данные: {timestamp_key: {varname: value, ...}}
# timestamp_key = то, что передано в set_value (float/int/etc)
self.data_rows = {}
# Внутренние структуры для генерации CSV-формата С
self._row_wall_dt = {} # {timestamp_key: datetime при первой записи}
self._base_ts = None # timestamp_key первой строки (число)
self._base_ts_val = 0.0 # float значение первой строки (для delta)
self._tick_x_start = 0 # начальный тик (можно менять вручную при необходимости)
# ---- Свойства ----
@property
def filename(self):
return self._filename
# ---- Публичные методы ----
def set_titles(self, varnames):
"""
Устанавливает имена переменных.
Формирует полные заголовки CSV в формате С-лога.
"""
if not isinstance(varnames, list):
raise TypeError("Varnames must be a list of strings.")
if not all(isinstance(name, str) for name in varnames):
raise ValueError("All variable names must be strings.")
self.variable_names_ordered = varnames
self.headers = ["Ticks(X)", "Ticks(Y)", "Time(Y)"] + self.variable_names_ordered
# Сброс данных (структура изменилась)
self.data_rows.clear()
self._row_wall_dt.clear()
self._base_ts = None
self._base_ts_val = 0.0
def set_value(self, timestamp, varname, varvalue):
"""
Установить ОДНО значение в ОДНУ колонку для заданного timestampа.
timestamp float секунд с эпохи (time.time()).
"""
if varname not in self.variable_names_ordered:
return # игнор, как у тебя было
# Новая строка?
if timestamp not in self.data_rows:
# Инициализируем поля переменных значением None
self.data_rows[timestamp] = {vn: None for vn in self.variable_names_ordered}
# Дата/время строки из ПЕРЕДАННОГО timestamp (а не datetime.now()!)
try:
ts_float = float(timestamp)
except Exception:
# если какая-то дичь прилетела, пусть будет 0 (эпоха) чтобы не упасть
ts_float = 0.0
self._row_wall_dt[timestamp] = datetime.fromtimestamp(ts_float)
# База для расчёта Ticks(Y) — первая строка
if self._base_ts is None:
self._base_ts = timestamp
self._base_ts_val = ts_float
# Записываем значение
self.data_rows[timestamp][varname] = varvalue
def select_file(self, parent=None) -> bool:
"""
Диалог выбора файла.
"""
options = QtWidgets.QFileDialog.Options()
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
parent,
"Сохранить данные CSV",
self._filename,
"CSV Files (*.csv);;All Files (*)",
options=options
)
if filename:
if not filename.lower().endswith('.csv'):
filename += '.csv'
self._filename = filename
return True
else:
return False
def write_to_csv(self):
"""
Формирует CSV в формате C:
Ticks(X);Ticks(Y);Time(Y);Var1;Var2;...
0;0,000000;22/07/2025 13:45:12:0123;...;...
Правила значений:
- Тик X: автоинкремент от 0 (или self._tick_x_start) по порядку сортировки timestamp.
- Ticks(Y): дельта (секунды,микросекунды) между текущим timestamp и первым timestamp.
- Time(Y): wallclock строки (datetime.now() при первом появлении timestamp).
- Значение < 0 -> пустая ячейка (как if(raw_data[i] >= 0) else ;)
- None -> пустая ячейка.
"""
if len(self.headers) <= 3: # только служебные поля без переменных
print("Ошибка: Заголовки не установлены или не содержат переменных. Вызовите set_titles() перед записью.")
return
if not self._filename:
print("Ошибка: Имя файла не определено. select_file() или задайте при инициализации.")
return
if not self.data_rows:
print("Предупреждение: Нет данных для записи.")
# всё равно создадим файл с одними заголовками
try:
with open(self._filename, 'w', newline='', encoding='utf-8') as csvfile:
# QUOTE_NONE + escapechar для чистого формата без кавычек (как в С-строке)
writer = csv.writer(
csvfile,
delimiter=self._delimiter,
quoting=csv.QUOTE_NONE,
escapechar='\\',
lineterminator='\r\n'
)
# Пишем заголовки
writer.writerow(self.headers)
if self.data_rows:
sorted_ts = sorted(self.data_rows.keys(), key=self._ts_sort_key)
# убедимся, что база была зафиксирована
if self._base_ts is None:
self._base_ts = sorted_ts[0]
self._base_ts_val = self._coerce_ts_to_float(self._base_ts)
tick_x = self._tick_x_start
for ts in sorted_ts:
row_dict = self.data_rows[ts]
# delta по timestamp
cur_ts_val = self._coerce_ts_to_float(ts)
delta_us = int(round((cur_ts_val - self._base_ts_val) * 1_000_000))
if delta_us < 0:
delta_us = 0 # защита
seconds = delta_us // 1_000_000
micros = delta_us % 1_000_000
# wallclock строки
dt = self._row_wall_dt.get(ts, datetime.now())
# Формат DD/MM/YYYY HH:MM:SS:мммм (4 цифры ms, как в C: us/1000)
time_str = dt.strftime("%d/%m/%Y %H:%M:%S") + f":{dt.microsecond // 1000:04d}"
# Значения
row_vals = []
for vn in self.variable_names_ordered:
v = row_dict.get(vn)
if v is None:
row_vals.append("") # нет данных
else:
# если числовое и <0 -> пусто (как в C: если raw_data[i] >= 0 else ;)
if isinstance(v, numbers.Number) and v < 0:
row_vals.append("")
else:
row_vals.append(v)
csv_row = [tick_x, f"{seconds},{micros:06d}", time_str] + row_vals
writer.writerow(csv_row)
tick_x += 1
print(f"Данные успешно записаны в '{self._filename}'")
except Exception as e:
print(f"Ошибка при записи в файл '{self._filename}': {e}")
# ---- Вспомогательные ----
def _coerce_ts_to_float(self, ts):
"""
Пробуем привести переданный timestamp к float.
Разрешаем int/float/str, остальное -> индекс по порядку (0).
"""
if isinstance(ts, numbers.Number):
return float(ts)
try:
return float(ts)
except Exception:
# fallback: нечисловой ключ -> используем порядковый индекс
# (таких почти не должно быть, но на всякий)
return 0.0
def _ts_sort_key(self, ts):
"""
Ключ сортировки timestampов сначала попытка float, потом str.
"""
if isinstance(ts, numbers.Number):
return (0, float(ts))
try:
return (0, float(ts))
except Exception:
return (1, str(ts))

View File

@ -12,9 +12,10 @@ from xml.dom import minidom
import myXML import myXML
import argparse import argparse
shortnameSize = 10
# === Словарь соответствия типов XML → DebugVarType_t === # === Словарь соответствия типов XML → DebugVarType_t ===
type_map = dict([ type_map_tms = dict([
*[(k, 'pt_int8') for k in ('signed char', 'char')], *[(k, 'pt_int8') for k in ('signed char', 'char')],
*[(k, 'pt_int16') for k in ('int', 'int16', 'short')], *[(k, 'pt_int16') for k in ('int', 'int16', 'short')],
*[(k, 'pt_int32') for k in ('long', 'int32', '_iqx')], *[(k, 'pt_int32') for k in ('long', 'int32', '_iqx')],
@ -52,6 +53,150 @@ type_map = dict([
('struct[]', 'pt_arr_struct'), ('struct[]', 'pt_arr_struct'),
('union[]', 'pt_arr_union'), ('union[]', 'pt_arr_union'),
]) ])
# === Словарь соответствия типов XML → DebugVarType_t ===
type_map_stm32 = dict([
*[(k, 'pt_int8') for k in (
'int8_t', 'signed char', 'char'
)],
# --- 8-bit unsigned ---
*[(k, 'pt_uint8') for k in (
'uint8_t', 'unsigned char'
)],
# --- 16-bit signed ---
*[(k, 'pt_int16') for k in (
'int16_t', 'short', 'short int', 'signed short', 'signed short int'
)],
# --- 16-bit unsigned ---
*[(k, 'pt_uint16') for k in (
'uint16_t', 'unsigned short', 'unsigned short int'
)],
# --- 32-bit signed ---
*[(k, 'pt_int32') for k in (
'int32_t', 'int', 'signed', 'signed int'
)],
# --- 32-bit unsigned ---
*[(k, 'pt_uint32') for k in (
'uint32_t', 'unsigned', 'unsigned int'
)],
# --- 64-bit signed ---
*[(k, 'pt_int64') for k in (
'int64_t', 'long long', 'signed long long', 'signed long long int'
)],
# --- 64-bit unsigned ---
*[(k, 'pt_uint64') for k in (
'uint64_t', 'unsigned long long', 'unsigned long long int'
)],
# --- Float ---
*[(k, 'pt_float') for k in (
'float', 'float32_t'
)],
# --- Struct and Union ---
('struct', 'pt_struct'),
('union', 'pt_union'),
('struct*', 'pt_ptr_struct'),
('union*', 'pt_ptr_union'),
('struct[]', 'pt_arr_struct'),
('union[]', 'pt_arr_union'),
# === POINTERS ===
# 8-bit
*[(k, 'pt_ptr_int8') for k in (
'int8_t*', 'signed char*', 'char*'
)],
*[(k, 'pt_ptr_uint8') for k in (
'uint8_t*', 'unsigned char*'
)],
# 16-bit
*[(k, 'pt_ptr_int16') for k in (
'int16_t*', 'short*', 'short int*', 'signed short*', 'signed short int*'
)],
*[(k, 'pt_ptr_uint16') for k in (
'uint16_t*', 'unsigned short*', 'unsigned short int*'
)],
# 32-bit
*[(k, 'pt_ptr_int32') for k in (
'int32_t*', 'int*', 'signed*', 'signed int*'
)],
*[(k, 'pt_ptr_uint32') for k in (
'uint32_t*', 'unsigned*', 'unsigned int*'
)],
# 64-bit
*[(k, 'pt_ptr_int64') for k in (
'int64_t*', 'long long*', 'signed long long*', 'signed long long int*'
)],
*[(k, 'pt_ptr_uint64') for k in (
'uint64_t*', 'unsigned long long*', 'unsigned long long int*'
)],
# float*
*[(k, 'pt_ptr_float') for k in (
'float*', 'float32_t*'
)],
# === ARRAYS ===
# 8-bit
*[(k, 'pt_arr_int8') for k in (
'int8_t[]', 'signed char[]', 'char[]'
)],
*[(k, 'pt_arr_uint8') for k in (
'uint8_t[]', 'unsigned char[]'
)],
# 16-bit
*[(k, 'pt_arr_int16') for k in (
'int16_t[]', 'short[]', 'short int[]', 'signed short[]', 'signed short int[]'
)],
*[(k, 'pt_arr_uint16') for k in (
'uint16_t[]', 'unsigned short[]', 'unsigned short int[]'
)],
# 32-bit
*[(k, 'pt_arr_int32') for k in (
'int32_t[]', 'int[]', 'signed[]', 'signed int[]'
)],
*[(k, 'pt_arr_uint32') for k in (
'uint32_t[]', 'unsigned[]', 'unsigned int[]'
)],
# 64-bit
*[(k, 'pt_arr_int64') for k in (
'int64_t[]', 'long long[]', 'signed long long[]', 'signed long long int[]'
)],
*[(k, 'pt_arr_uint64') for k in (
'uint64_t[]', 'unsigned long long[]', 'unsigned long long int[]'
)],
# float[]
*[(k, 'pt_arr_float') for k in (
'float[]', 'float32_t[]'
)],
])
type_map = type_map_tms
stm_flag_global = 0
def choose_type_map(stm_flag):
global type_map # объявляем, что будем менять глобальную переменную
global stm_flag_global # объявляем, что будем менять глобальную переменную
if stm_flag:
type_map = type_map_stm32
stm_flag_global = 1
else:
type_map = type_map_tms
stm_flag_global = 0
def map_type_to_pt(typename, varname=None, typedef_map=None): def map_type_to_pt(typename, varname=None, typedef_map=None):
typename_orig = typename.strip() typename_orig = typename.strip()
@ -140,15 +285,20 @@ def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
Возвращает True если что-то добавлено и XML перезаписан, иначе False. Возвращает True если что-то добавлено и XML перезаписан, иначе False.
""" """
pattern = re.compile(
r'{\s*\(uint8_t\s*\*\)\s*&([a-zA-Z_][a-zA-Z0-9_]*(?:\[.*?\])?(?:(?:\.|->)[a-zA-Z_][a-zA-Z0-9_]*(?:\[.*?\])?)*)\s*,\s*'
r'(pt_\w+)\s*,\s*'
r'(t?_?iq\w+)\s*,\s*'
r'(t?_?iq\w+)\s*,\s*'
r'"([^"]+)"'
)
# Считываем существующие переменные # Считываем существующие переменные
parsed_vars = {} parsed_vars = {}
if os.path.isfile(output_path): if os.path.isfile(output_path):
with open(output_path, 'r', encoding='utf-8', errors='ignore') as f: with open(output_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f: for line in f:
# {(char *)&some.deep.var.name , pt_uint16 , t_iq15 , "ShortName"}, # {(uint8_t *)&some.deep.var.name , pt_uint16 , t_iq15 , t_iq10, "ShortName"},
m = re.match( m = pattern.search(line)
r'{\s*\(char\s*\*\)\s*&([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*,\s*(pt_\w+)\s*,\s*(t?iq_\w+)\s*,\s*"([^"]+)"',
line)
if m: if m:
full_varname = m.group(1) # e.g., some.deep.var.name full_varname = m.group(1) # e.g., some.deep.var.name
pt_type = m.group(2) pt_type = m.group(2)
@ -285,12 +435,27 @@ def read_vars_from_xml(proj_path, xml_rel_path):
return unique_vars, include_files, vars_need_extern return unique_vars, include_files, vars_need_extern
def read_file_try_encodings(filepath):
if not os.path.isfile(filepath):
# Файл не существует — просто вернуть пустую строку или None
return "", None
for enc in ['utf-8', 'cp1251']:
try:
with open(filepath, 'r', encoding=enc) as f:
content = f.read()
return content, enc
except UnicodeDecodeError:
continue
raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251")
def generate_vars_file(proj_path, xml_path, output_dir): def generate_vars_file(proj_path, xml_path, output_dir):
output_dir = os.path.join(proj_path, output_dir) output_dir = os.path.join(proj_path, output_dir)
os.makedirs(output_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True)
output_path = os.path.join(output_dir, 'debug_vars.c') output_path = os.path.join(output_dir, 'debug_vars.c')
LIBC_path = os.path.join(output_dir, 'debug_tools.c')
LIBH_path = os.path.join(output_dir, 'debug_tools.h')
# Запись новых переменных для в XML # Запись новых переменных для в XML
@ -338,7 +503,7 @@ def generate_vars_file(proj_path, xml_path, output_dir):
# Дополнительные поля, например комментарий # Дополнительные поля, например комментарий
comment = info.get("comment", "") comment = info.get("comment", "")
short_name = info.get("shortname", f'"{vname}"') short_name = info.get("shortname", f'"{vname}"')
short_trimmed = short_name[:10] # ограничиваем длину до 10 short_trimmed = short_name[:shortnameSize] # ограничиваем длину до 10
if pt_type not in ('pt_struct', 'pt_union'): if pt_type not in ('pt_struct', 'pt_union'):
f_name = f'{vname},' f_name = f'{vname},'
@ -348,7 +513,7 @@ def generate_vars_file(proj_path, xml_path, output_dir):
f_short_name = f'"{short_trimmed}"' # оборачиваем в кавычки f_short_name = f'"{short_trimmed}"' # оборачиваем в кавычки
# Добавим комментарий после записи, если он есть # Добавим комментарий после записи, если он есть
comment_str = f' // {comment}' if comment else '' comment_str = f' // {comment}' if comment else ''
line = f'{{(char *)&{f_name:<57} {f_type:<15} {f_iq:<15} {f_ret_iq:<15} {f_short_name:<21}}}, \\{comment_str}' line = f'{{(uint8_t *)&{f_name:<58} {f_type:<15} {f_iq:<15} {f_ret_iq:<15} {f_short_name:<21}}}, \\{comment_str}'
new_debug_vars[vname] = line new_debug_vars[vname] = line
else: else:
@ -394,14 +559,19 @@ def generate_vars_file(proj_path, xml_path, output_dir):
out_lines.append(f'\n\n// Определение массива с указателями на переменные для отладки') out_lines.append(f'\n\n// Определение массива с указателями на переменные для отладки')
out_lines.append(f'int DebugVar_Qnt = {len(all_debug_lines)};') out_lines.append(f'int DebugVar_Qnt = {len(all_debug_lines)};')
out_lines.append('#pragma DATA_SECTION(dbg_vars,".dbgvar_info")') if stm_flag_global == 0:
out_lines.append('#pragma DATA_SECTION(dbg_vars,".dbgvar_info")')
out_lines.append('// pointer_type iq_type return_iq_type short_name')
out_lines.append('DebugVar_t dbg_vars[] = {\\') out_lines.append('DebugVar_t dbg_vars[] = {\\')
out_lines.extend(all_debug_lines) out_lines.extend(all_debug_lines)
out_lines.append('};') out_lines.append('};')
out_lines.append('') out_lines.append('')
# Выберем кодировку для записи файла # Выберем кодировку для записи файла
# Если встречается несколько, возьмем первую из set # Если встречается несколько, возьмем первую из set
enc_to_write = 'cp1251' if stm_flag_global == 0:
enc_to_write = 'cp1251'
else:
enc_to_write = 'utf-8'
#print("== GLOBAL VARS FOUND ==") #print("== GLOBAL VARS FOUND ==")
#for vname, (vtype, path) in vars_in_c.items(): #for vname, (vtype, path) in vars_in_c.items():
@ -411,6 +581,16 @@ def generate_vars_file(proj_path, xml_path, output_dir):
with open(output_path, 'w', encoding=enc_to_write) as f: with open(output_path, 'w', encoding=enc_to_write) as f:
f.write('\n'.join(out_lines)) f.write('\n'.join(out_lines))
if os.path.isfile(LIBC_path):
libc_code, _ = read_file_try_encodings(LIBC_path)
with open(LIBC_path, 'w', encoding=enc_to_write) as f:
f.write(libc_code)
if os.path.isfile(LIBH_path):
libh_code, _ = read_file_try_encodings(LIBH_path)
with open(LIBH_path, 'w', encoding=enc_to_write) as f:
f.write(libh_code)
print(f'Файл debug_vars.c сгенерирован в кодировке, переменных: {len(all_debug_lines)}') print(f'Файл debug_vars.c сгенерирован в кодировке, переменных: {len(all_debug_lines)}')
@ -472,16 +652,17 @@ Usage example:
print(f"Error: Project path '{proj_path}' не является директорией или не существует.") print(f"Error: Project path '{proj_path}' не является директорией или не существует.")
sys.exit(1) sys.exit(1)
generate_vars_file(proj_path, xml_path_rel, output_dir_rel) generate_vars_file(proj_path, xml_path_rel, output_dir_rel, 0)
if __name__ == "__main__": if __name__ == "__main__":
main() main()
def run_generate(proj_path, xml_path, output_dir): def run_generate(proj_path, xml_path, output_dir, shortname_size):
import os import os
global shortnameSize
shortnameSize = shortname_size
# Normalize absolute paths # Normalize absolute paths
proj_path = os.path.abspath(proj_path) proj_path = os.path.abspath(proj_path)
xml_path_abs = os.path.abspath(xml_path) xml_path_abs = os.path.abspath(xml_path)

View File

@ -1,5 +1,6 @@
import os import os
import re import re
from lxml import etree as ET
def strip_single_line_comments(code): def strip_single_line_comments(code):
@ -66,89 +67,176 @@ def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
return include_files return include_files
def parse_objects_list(objects_list_path, project_root):
c_files = []
include_dirs = set()
if not os.path.isfile(objects_list_path):
return c_files, include_dirs
with open(objects_list_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
line = line.strip().strip('"').replace("\\", "/")
if line.endswith(".o"):
c_file = re.sub(r"\.o$", ".c", line)
abs_path = os.path.normpath(os.path.join(project_root, c_file))
if os.path.isfile(abs_path):
if not any(x in abs_path for x in ["DebugTools", "v120", "v100"]):
c_files.append(abs_path)
include_dirs.add(os.path.dirname(abs_path))
return c_files, include_dirs
def parse_uvprojx(uvprojx_path):
import xml.etree.ElementTree as ET
import os
tree = ET.parse(uvprojx_path)
root = tree.getroot()
project_dir = os.path.dirname(os.path.abspath(uvprojx_path))
c_files = []
include_dirs = set()
defines = set()
# Найдём C-файлы и директории
for file_elem in root.findall(".//FilePath"):
file_path = file_elem.text
if file_path:
abs_path = os.path.normpath(os.path.join(project_dir, file_path))
if os.path.isfile(abs_path):
if abs_path.endswith(".c"):
c_files.append(abs_path)
include_dirs.add(os.path.dirname(abs_path))
# Включаем IncludePath
for inc_path_elem in root.findall(".//IncludePath"):
path_text = inc_path_elem.text
if path_text:
paths = path_text.split(';')
for p in paths:
p = p.strip()
if p:
abs_inc_path = os.path.normpath(os.path.join(project_dir, p))
if os.path.isdir(abs_inc_path):
include_dirs.add(abs_inc_path)
# Добавим <Define>
for define_elem in root.findall(".//Define"):
def_text = define_elem.text
if def_text:
for d in def_text.split(','):
d = d.strip()
if d:
defines.add(d)
h_files = find_all_includes_recursive(c_files, include_dirs)
return sorted(c_files), sorted(h_files), sorted(include_dirs), sorted(defines)
def parse_makefile(makefile_path, proj_path): def parse_makefile(makefile_path, proj_path):
makefile_dir = os.path.dirname(makefile_path) import os
project_root = proj_path import re
project_root = os.path.abspath(proj_path)
c_files = []
include_dirs = set()
defines = [] # Заглушка: нет define-параметров из Makefile
with open(makefile_path, 'r', encoding='utf-8') as f: with open(makefile_path, 'r', encoding='utf-8') as f:
lines = f.readlines() lines = f.readlines()
objs_lines = [] raw_entries = []
collecting = False collecting = False
for line in lines: for line in lines:
stripped = line.strip() stripped = line.strip()
if stripped.startswith("ORDERED_OBJS") and "+=" in stripped:
parts = stripped.split("\\") if (("ORDERED_OBJS" in stripped or "C_SOURCES" in stripped) and ("+=" in stripped or "=" in stripped)):
first_part = parts[0]
idx = first_part.find("+=")
tail = first_part[idx+2:].strip()
if tail:
objs_lines.append(tail)
collecting = True collecting = True
if len(parts) > 1:
for p in parts[1:]:
p = p.strip()
if p:
objs_lines.append(p)
continue
if collecting: if collecting:
if stripped.endswith("\\"): line_clean = stripped.rstrip("\\").strip()
objs_lines.append(stripped[:-1].strip()) if line_clean:
else: line_clean = re.sub(r"\$\([^)]+\)", "", line_clean)
objs_lines.append(stripped) line_clean = re.sub(r"\$\{[^}]+\}", "", line_clean)
raw_entries.append(line_clean)
if not stripped.endswith("\\"):
collecting = False collecting = False
objs_str = ' '.join(objs_lines) for entry in raw_entries:
for token in entry.split():
token = token.strip('"')
if not token:
continue
objs_str = re.sub(r"\$\([^)]+\)", "", objs_str) token = token.replace("\\", "/")
objs = [] if token.endswith(".obj"):
for part in objs_str.split(): token = re.sub(r"\.obj$", ".c", token)
part = part.strip() elif token.endswith(".o"):
if part.startswith('"') and part.endswith('"'): token = re.sub(r"\.o$", ".c", token)
part = part[1:-1]
if part:
objs.append(part)
c_files = [] if token.endswith(".c"):
include_dirs = set() abs_path = os.path.normpath(os.path.join(project_root, token))
if os.path.isfile(abs_path):
if not any(x in abs_path for x in ["DebugTools", "v120", "v100"]):
c_files.append(abs_path)
include_dirs.add(os.path.dirname(abs_path))
for obj_path in objs: if not c_files:
if "DebugTools" in obj_path: makefile_dir = os.path.dirname(os.path.abspath(makefile_path))
continue objects_list_path = os.path.join(makefile_dir, "objects.list")
if "v120" in obj_path: c_from_objects, inc_from_objects = parse_objects_list(objects_list_path, project_root)
continue c_files.extend(c_from_objects)
if "v100" in obj_path: include_dirs.update(inc_from_objects)
continue
if obj_path.startswith("Debug\\") or obj_path.startswith("Debug/"): for line in lines:
rel_path = obj_path.replace("Debug\\", "Src\\").replace("Debug/", "Src/") if "-I" in line or "C_INCLUDES" in line:
else: matches = re.findall(r"-I\s*([^\s\\]+)", line)
rel_path = obj_path for match in matches:
match = match.strip('"').replace("\\", "/")
abs_include = os.path.normpath(os.path.join(project_root, match))
if os.path.isdir(abs_include):
include_dirs.add(abs_include)
abs_path = os.path.normpath(os.path.join(project_root, rel_path)) # Добавляем пути с заменой 'Src' на 'Inc', если путь заканчивается на 'Src'
additional_includes = set()
root, ext = os.path.splitext(abs_path) for inc in include_dirs:
if ext.lower() == ".obj": if inc.endswith(os.sep + "Src") or inc.endswith("/Src"):
c_path = root + ".c" inc_inc = inc[:-3] + "Inc" # заменяем 'Src' на 'Inc'
else: if os.path.isdir(inc_inc):
c_path = abs_path additional_includes.add(inc_inc)
# Проверяем существование файла, если нет — пропускаем
if not os.path.isfile(c_path):
continue
# Сохраняем только .c файлы
if c_path.lower().endswith(".c"):
c_files.append(c_path)
dir_path = os.path.dirname(c_path)
if dir_path and "DebugTools" not in dir_path:
include_dirs.add(dir_path)
include_dirs.update(additional_includes)
h_files = find_all_includes_recursive(c_files, include_dirs) h_files = find_all_includes_recursive(c_files, include_dirs)
return sorted(c_files), sorted(h_files), sorted(include_dirs), sorted(defines)
return sorted(c_files), sorted(h_files), sorted(include_dirs)
def parse_project(project_file_path, project_root=None):
"""
Выбирает парсер в зависимости от расширения project_file_path:
- для *.uvprojx и *.uvproj вызывается парсер Keil
- для остальных - parse_makefile
project_root нужен для parse_makefile, если не передан - берется из project_file_path
"""
ext = os.path.splitext(project_file_path)[1].lower()
if ext in ['.uvprojx', '.uvproj']:
# Парсим Keil проект
return parse_uvprojx(project_file_path)
else:
# Парсим makefile
if project_root is None:
project_root = os.path.dirname(os.path.abspath(project_file_path))
return parse_makefile(project_file_path, project_root)

287
Src/path_hints.py Normal file
View File

@ -0,0 +1,287 @@
# path_hints.py
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
import re
# ---------------------- tokenization helpers ----------------------
def split_path_tokens(path: str) -> List[str]:
"""
Разбивает строку пути на логические части:
'foo[2].bar[1]->baz' -> ['foo', '[2]', 'bar', '[1]', 'baz']
Аналог твоей split_path(), но оставлена как чистая функция.
"""
tokens: List[str] = []
token = ''
i = 0
L = len(path)
while i < L:
c = path[i]
# '->'
if c == '-' and i + 1 < L and path[i:i+2] == '->':
if token:
tokens.append(token)
token = ''
i += 2
continue
# одиночный '-' в конце
if c == '-' and i == L - 1:
i += 1
continue
# '.'
if c == '.':
if token:
tokens.append(token)
token = ''
i += 1
continue
# '[' ... ']'
if c == '[':
if token:
tokens.append(token)
token = ''
idx = ''
while i < L and path[i] != ']':
idx += path[i]
i += 1
if i < L and path[i] == ']':
idx += ']'
i += 1
tokens.append(idx)
continue
# обычный символ
token += c
i += 1
if token:
tokens.append(token)
return tokens
def canonical_key(path: str) -> str:
"""
Преобразует путь к канонической форме для индекса / поиска:
- '->' -> '.'
- '[' -> '.['
- lower()
"""
p = path.replace('->', '.')
p = p.replace('[', '.[')
return p.lower()
# ---------------------- индекс узлов ----------------------
@dataclass
class PathNode:
"""
Узел в логическом дереве путей.
Храним:
- собственное имя (локальное, напр. 'controller' или '[3]')
- полный путь (оригинальный, как его должен видеть пользователь)
- тип (опционально; widget может хранить отдельно)
- дети
"""
name: str
full_path: str
type_str: str = ''
children: Dict[str, "PathNode"] = field(default_factory=dict)
def add_child(self, child: "PathNode") -> None:
self.children[child.name] = child
def get_children(self) -> List["PathNode"]:
"""
Вернуть список дочерних узлов, отсортированных по имени.
"""
return sorted(self.children.values(), key=lambda n: n.name)
class PathHints:
"""
Движок автоподсказок / completion.
Работает с плоским списком ПОЛНЫХ имён (как показываются пользователю).
Сам восстанавливает иерархию и выдаёт подсказки по текущему вводу.
Qt-независим.
"""
def __init__(self) -> None:
self._paths: List[str] = []
self._types: Dict[str, str] = {} # full_path -> type_str (опционально)
self._index: Dict[str, PathNode] = {} # canonical full path -> node
self._root_children: Dict[str, PathNode] = {} # top-level по первому токену
# ------------ Подаём данные ------------
def set_paths(self,
paths: List[Tuple[str, Optional[str]]]
) -> None:
"""
paths: список кортежей (full_path, type_str|None).
Пример: ('project.controller.read.errors.bit.status_er0', 'unsigned int')
Поля могут содержать '->' и индексы, т.е. строки в пользовательском формате.
NOTE: порядок не важен; дерево строится автоматически.
"""
self._paths = []
self._types.clear()
self._index.clear()
self._root_children.clear()
for p, t in paths:
if t is None:
t = ''
self._add_path(p, t)
def _add_path(self, full_path: str, type_str: str) -> None:
self._paths.append(full_path)
self._types[full_path] = type_str
toks = split_path_tokens(full_path)
if not toks:
return
cur_dict = self._root_children
cur_full = ''
parent_node: Optional[PathNode] = None
for i, tok in enumerate(toks):
# Собираем ПОЛНЫЙ путь
if cur_full == '':
cur_full = tok
else:
if tok.startswith('['):
cur_full += tok
else:
cur_full += '.' + tok
# Если узел уже есть
node = cur_dict.get(tok)
if node is None:
# --- ВАЖНО: full_path = cur_full ---
node = PathNode(name=tok, full_path=cur_full)
cur_dict[tok] = node
# Регистрируем все узлы, включая промежуточные
self._index[canonical_key(cur_full)] = node
parent_node = node
cur_dict = node.children
# В последний узел добавляем тип
if parent_node:
parent_node.type_str = type_str
# ------------ Поиск узла ------------
def find_node(self, path: str) -> Optional[PathNode]:
return self._index.get(canonical_key(path))
def get_children(self, full_path: str) -> List[PathNode]:
"""
Вернуть список дочерних узлов PathNode для заданного полного пути.
Если узел не найден вернуть пустой список.
"""
node = self.find_node(full_path)
if node is None:
return []
return node.get_children()
# ------------ Подсказки ------------
def suggest(self,
text: str,
*,
include_partial: bool = True
) -> List[str]:
"""
Вернёт список *полных имён узлов*, подходящих под ввод.
Правила (упрощённо, повторяя твою update_completions()):
- Если текст пуст top-level.
- Если заканчивается на '.' или '->' или '[' вернуть детей текущего узла.
- Иначе фильтр по последнему фрагменту (prefix substring match).
"""
text = text or ''
stripped = text.strip()
# пусто: top-level
if stripped == '':
return sorted(self._root_full_names())
# Завершение по разделителю?
if stripped.endswith('.') or stripped.endswith('->') or stripped.endswith('['):
base = stripped[:-1] if stripped.endswith('[') else stripped.rstrip('.').rstrip('>').rstrip('-')
node = self.find_node(base)
if node:
return self._children_full_names(node)
# не нашли базу — ничего
return []
# иначе: обычный поиск по последней части
toks = split_path_tokens(stripped)
prefix_last = toks[-1].lower() if toks else ''
parent_toks = toks[:-1]
if not parent_toks:
# фильтр top-level
res = []
for name, node in self._root_children.items():
if prefix_last == '' or prefix_last in name.lower():
res.append(node.full_path)
return sorted(res)
# есть родитель
parent_path = self._join_tokens(parent_toks)
parent_node = self.find_node(parent_path)
if not parent_node:
return []
res = []
for child in parent_node.children.values():
if prefix_last == '' or prefix_last in child.name.lower():
res.append(child.full_path)
return sorted(res)
def add_separator(self, full_path: str) -> str:
"""
Возвращает full_path с добавленным разделителем ('.' или '['),
если у узла есть дети и пользователь ещё не поставил разделитель.
Если первый ребёнок массивный токен ('[0]') добавляем '['.
Позже можно допилить '->' для указателей.
"""
node = self.find_node(full_path)
text = full_path
if node and node.children and not (
text.endswith('.') or text.endswith('->') or text.endswith('[')
):
first_child = next(iter(node.children.values()))
if first_child.name.startswith('['):
text += '[' # сразу начинаем индекс
else:
text += '.' # обычный переход
return text
# ------------ внутренние вспомогательные ------------
def _root_full_names(self) -> List[str]:
return [node.full_path for node in self._root_children.values()]
def _children_full_names(self, node: PathNode) -> List[str]:
return [ch.full_path for ch in node.children.values()]
@staticmethod
def _join_tokens(tokens: List[str]) -> str:
"""
Собираем путь обратно. Для внутренних нужд (поиск), формат не критичен
всё равно canonical_key() нормализует.
"""
if not tokens:
return ''
out = tokens[0]
for t in tokens[1:]:
if t.startswith('['):
out += t
else:
out += '.' + t
return out

View File

@ -11,7 +11,7 @@ from clang import cindex
from clang.cindex import Config from clang.cindex import Config
import lxml.etree as ET import lxml.etree as ET
from xml.dom import minidom from xml.dom import minidom
from makefile_parser import parse_makefile from makefile_parser import parse_project
from collections import deque from collections import deque
import argparse import argparse
import myXML import myXML
@ -118,11 +118,11 @@ def get_canonical_typedef_file(var_type, include_dirs):
break break
return None return None
def analyze_variables_across_files(c_files, h_files, include_dirs): def analyze_variables_across_files(c_files, h_files, include_dirs, global_defs):
optional_printf(PRINT_STATUS, "Starting analysis of variables across files...") optional_printf(PRINT_STATUS, "Starting analysis of variables across files...")
index = clang.cindex.Index.create() index = clang.cindex.Index.create()
args = [f"-I{inc}" for inc in include_dirs] define_args = [f"-D{d}" for d in global_defs]
args = [f"-I{inc}" for inc in include_dirs] + define_args
unique_vars = {} # имя переменной → словарь с инфой unique_vars = {} # имя переменной → словарь с инфой
h_files_needed = set() h_files_needed = set()
vars_need_extern = {} # имя переменной → словарь без поля 'extern' vars_need_extern = {} # имя переменной → словарь без поля 'extern'
@ -154,6 +154,7 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
# Проверяем, начинается ли имя с "_" и содержит заглавные буквы или служебные символы # Проверяем, начинается ли имя с "_" и содержит заглавные буквы или служебные символы
return bool(re.match(r"^_[_A-Z]", var_name)) return bool(re.match(r"^_[_A-Z]", var_name))
if node.kind == clang.cindex.CursorKind.VAR_DECL: if node.kind == clang.cindex.CursorKind.VAR_DECL:
if node.semantic_parent.kind == clang.cindex.CursorKind.TRANSLATION_UNIT: if node.semantic_parent.kind == clang.cindex.CursorKind.TRANSLATION_UNIT:
is_extern = (node.storage_class == clang.cindex.StorageClass.EXTERN) is_extern = (node.storage_class == clang.cindex.StorageClass.EXTERN)
@ -170,7 +171,12 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
return # игнорируем только явно известные служебные переменные return # игнорируем только явно известные служебные переменные
if node.spelling == 'HUGE': # еще одна служеюная, которую хз как выделять if node.spelling == 'HUGE': # еще одна служеюная, которую хз как выделять
return return
if 'Drivers' in node.location.file.name:
return
if 'uint' in node.spelling:
a = 1
# Проверяем, является ли тип указателем на функцию # Проверяем, является ли тип указателем на функцию
# Признак: в типе есть '(' и ')' и '*', например: "void (*)(int)" # Признак: в типе есть '(' и ')' и '*', например: "void (*)(int)"
if "(" in var_type and "*" in var_type and ")" in var_type: if "(" in var_type and "*" in var_type and ")" in var_type:
@ -295,6 +301,8 @@ def strip_ptr_and_array(typename):
return typename return typename
def analyze_typedefs_and_struct(typedefs, structs): def analyze_typedefs_and_struct(typedefs, structs):
optional_printf(PRINT_STATUS, "Resolving typedefs and expanding struct field types...") optional_printf(PRINT_STATUS, "Resolving typedefs and expanding struct field types...")
@ -422,10 +430,28 @@ def contains_anywhere_in_node(node, target: str) -> bool:
return False return False
def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
def try_guess_std_include():
# Популярные места, где может лежать stdint.h
guesses = [
r"C:\Keil_v5\ARM\ARMCLANG\include",
r"C:\Program Files (x86)\GNU Arm Embedded Toolchain",
r"C:\Program Files (x86)\Arm GNU Toolchain"
]
found = []
for base in guesses:
for root, dirs, files in os.walk(base):
if "stdint.h" in files:
found.append(root)
return found
def analyze_typedefs_and_structs_across_files(c_files, include_dirs, global_defs):
optional_printf(PRINT_STATUS, "Starting analysis of typedefs and structs across files...") optional_printf(PRINT_STATUS, "Starting analysis of typedefs and structs across files...")
index = clang.cindex.Index.create() index = clang.cindex.Index.create()
args = [f"-I{inc}" for inc in include_dirs] define_args = [f"-D{d}" for d in global_defs]
extra_std_include_dirs = try_guess_std_include()
args = [f"-I{inc}" for inc in include_dirs] + extra_std_include_dirs + define_args
unique_typedefs_raw = {} unique_typedefs_raw = {}
unique_structs_raw = {} unique_structs_raw = {}
@ -452,7 +478,6 @@ def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
raw_name = node.spelling raw_name = node.spelling
normalized_name = normalize_type_name(raw_name) normalized_name = normalize_type_name(raw_name)
# struct_name всегда с префиксом # struct_name всегда с префиксом
if node.spelling and "unnamed" not in normalized_name: if node.spelling and "unnamed" not in normalized_name:
struct_name = f"{prefix}{normalized_name}" struct_name = f"{prefix}{normalized_name}"
@ -855,10 +880,10 @@ Usage example:
print(f"Error: Makefile path '{makefile_path}' does not exist.") print(f"Error: Makefile path '{makefile_path}' does not exist.")
sys.exit(1) sys.exit(1)
c_files, h_files, include_dirs = parse_makefile(makefile_path, proj_path) c_files, h_files, include_dirs, global_defs = parse_project(makefile_path, proj_path)
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs) vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs, global_defs)
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs) typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs, global_defs)
vars = dict(sorted(vars.items())) vars = dict(sorted(vars.items()))
includes = get_sorted_headers(c_files, includes, include_dirs) includes = get_sorted_headers(c_files, includes, include_dirs)
@ -898,10 +923,10 @@ def run_scan(proj_path, makefile_path, output_xml, verbose=2):
if not os.path.isfile(makefile_path): if not os.path.isfile(makefile_path):
raise FileNotFoundError(f"Makefile path '{makefile_path}' does not exist.") raise FileNotFoundError(f"Makefile path '{makefile_path}' does not exist.")
c_files, h_files, include_dirs = parse_makefile(makefile_path, proj_path) c_files, h_files, include_dirs, global_defs = parse_project(makefile_path, proj_path)
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs) vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs, global_defs)
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs) typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs, global_defs)
vars = dict(sorted(vars.items())) vars = dict(sorted(vars.items()))
includes = get_sorted_headers(c_files, includes, include_dirs) includes = get_sorted_headers(c_files, includes, include_dirs)

View File

@ -0,0 +1,504 @@
"""
LowLevelSelectorWidget (refactored)
-----------------------------------
Версия, использующая VariableTableWidget вместо самодельной таблицы selected_vars_table.
Ключевые изменения:
* Вместо QTableWidget с 6 колонками теперь встраивается VariableTableWidget (8 колонок: , En, Name, Origin Type, Base Type, IQ Type, Return Type, Short Name).
* Логика sync <-> self._all_available_vars перенесена в _on_var_table_changed() и _pull_from_var_table().
* Поддержка политики хранения типов:
- ptr_type: строковое имя (без префикса `pt_`).
- ptr_type_enum: числовой индекс (см. PT_ENUM_ORDER).
- Для совместимости с VariableTableWidget: поле `pt_type` = 'pt_<name>'.
- IQ / Return: аналогично (`iq_type` / `iq_type_enum`, `return_type` / `return_type_enum`).
* Функции получения выбранных переменных теперь читают данные из VariableTableWidget.
* Убраны неиспользуемые методы, связанные с прежней таблицей (комбобоксы и т.п.).
Как интегрировать:
1. Поместите этот файл рядом с module VariableTableWidget (см. импорт ниже). Если класс VariableTableWidget находится в том же файле удалите строку импорта и используйте напрямую.
2. Убедитесь, что VariablesXML предоставляет методы get_all_vars_data() (list[dict]) и, при наличии, get_struct_map() -> dict[type_name -> dict[field_name -> field_type]]. Если такого метода нет, передаём пустой {} и автодополнение по структурам будет недоступно.
3. Отметьте переменные в VariableSelectorDialog (как и раньше) он обновит self._all_available_vars. После закрытия диалога вызывается self._populate_var_table().
4. Для чтения выбранных переменных используйте get_selected_variables_and_addresses(); она вернёт список словарей в унифицированном формате.
Примечание о совместимости: VariableTableWidget работает с ключами `pt_type`, `iq_type`, `return_type` (строки с префиксами). Мы поддерживаем дублирование этих полей с «новыми» полями без префикса и enumзначениями.
"""
from __future__ import annotations
import sys
import re
import datetime
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple, Any
from PySide2 import QtCore, QtGui
from PySide2.QtWidgets import (
QWidget, QVBoxLayout, QPushButton, QLabel, QHBoxLayout, QFileDialog, QMessageBox,
QMainWindow, QApplication, QSizePolicy, QSpinBox, QGroupBox, QSplitter, QFormLayout
)
# Локальные импорты
from path_hints import PathHints
from generate_debug_vars import choose_type_map, type_map
from var_selector_window import VariableSelectorDialog
from allvars_xml_parser import VariablesXML
# Импортируем готовую таблицу
# ЗАМЕТКА: замените на реальное имя файла/модуля, если отличается.
from var_table import VariableTableWidget, rows as VT_ROWS # noqa: F401
# ------------------------------------------------------------ Enumerations --
# Порядок фиксируем на основании предыдущей версии. При необходимости расширьте.
PT_ENUM_ORDER = [
'unknown','int8','int16','int32','int64',
'uint8','uint16','uint32','uint64','float',
'struct','union'
]
IQ_ENUM_ORDER = [
'iq_none','iq','iq1','iq2','iq3','iq4','iq5','iq6',
'iq7','iq8','iq9','iq10','iq11','iq12','iq13','iq14',
'iq15','iq16','iq17','iq18','iq19','iq20','iq21','iq22',
'iq23','iq24','iq25','iq26','iq27','iq28','iq29','iq30'
]
PT_ENUM_VALUE: Dict[str, int] = {name: idx for idx, name in enumerate(PT_ENUM_ORDER)}
IQ_ENUM_VALUE: Dict[str, int] = {name: idx for idx, name in enumerate(IQ_ENUM_ORDER)}
PT_ENUM_NAME_FROM_VAL: Dict[int, str] = {v: k for k, v in PT_ENUM_VALUE.items()}
IQ_ENUM_NAME_FROM_VAL: Dict[int, str] = {v: k for k, v in IQ_ENUM_VALUE.items()}
# ------------------------------------------- Address / validation helpers --
HEX_ADDR_MASK = QtCore.QRegExp(r"0x[0-9A-Fa-f]{0,6}")
class HexAddrValidator(QtGui.QRegExpValidator):
def __init__(self, parent=None):
super().__init__(HEX_ADDR_MASK, parent)
@staticmethod
def normalize(text: str) -> str:
if not text:
return '0x000000'
try:
val = int(text,16)
except ValueError:
return '0x000000'
return f"0x{val & 0xFFFFFF:06X}"
class LowLevelSelectorWidget(QWidget):
variablePrepared = QtCore.Signal(dict)
xmlLoaded = QtCore.Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle('LowLevel Variable Selector')
self._xml: Optional[VariablesXML] = None
self._paths: List[str] = []
self._path_info: Dict[str, Tuple[int, str]] = {}
self._addr_index: Dict[int, Optional[str]] = {}
self._hints = PathHints()
self._all_available_vars: List[Dict[str, Any]] = []
self.dt = None
self.flat_vars = None
# --- NEW ---
self.btn_read_once = QPushButton("Read Once")
self.btn_start_polling = QPushButton("Start Polling")
self.spin_interval = QSpinBox()
self.spin_interval.setRange(50, 10000)
self.spin_interval.setValue(500)
self.spin_interval.setSuffix(" ms")
self._build_ui()
self._connect()
def _build_ui(self):
tab = QWidget()
main_layout = QVBoxLayout(tab)
# --- Variable Selector ---
g_selector = QGroupBox("Variable Selector")
selector_layout = QVBoxLayout(g_selector)
form_selector = QFormLayout()
# --- XML File chooser ---
file_layout = QHBoxLayout()
self.btn_load = QPushButton('Load XML...')
self.lbl_file = QLabel('<no file>')
self.lbl_file.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
file_layout.addWidget(self.btn_load)
file_layout.addWidget(self.lbl_file, 1)
form_selector.addRow("XML File:", file_layout)
# --- Interval SpinBox ---
self.spin_interval = QSpinBox()
self.spin_interval.setRange(50, 10000)
self.spin_interval.setValue(500)
self.spin_interval.setSuffix(" ms")
form_selector.addRow("Interval:", self.spin_interval)
selector_layout.addLayout(form_selector)
# --- Buttons ---
self.btn_read_once = QPushButton("Read Once")
self.btn_start_polling = QPushButton("Start Polling")
btn_layout = QHBoxLayout()
btn_layout.addWidget(self.btn_read_once)
btn_layout.addWidget(self.btn_start_polling)
selector_layout.addLayout(btn_layout)
# --- Table ---
g_table = QGroupBox("Table")
table_layout = QVBoxLayout(g_table)
self.btn_open_var_selector = QPushButton("Выбрать переменные...")
table_layout.addWidget(self.btn_open_var_selector)
self.var_table = VariableTableWidget(self, show_value_instead_of_shortname=1)
self.var_table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
table_layout.addWidget(self.var_table)
# --- Timestamp (moved here) ---
self.lbl_timestamp = QLabel('Timestamp: -')
table_layout.addWidget(self.lbl_timestamp)
# --- Splitter (Selector + Table) ---
v_split = QSplitter(QtCore.Qt.Vertical)
v_split.addWidget(g_selector)
v_split.addWidget(g_table)
v_split.setStretchFactor(0, 1)
v_split.setStretchFactor(1, 3)
main_layout.addWidget(v_split)
self.setLayout(main_layout)
def _connect(self):
self.btn_load.clicked.connect(self._on_load_xml)
self.btn_open_var_selector.clicked.connect(self._on_open_variable_selector)
# ------------------------------------------------------ XML loading ----
def _on_load_xml(self):
path, _ = QFileDialog.getOpenFileName(
self, 'Select variables XML', '', 'XML Files (*.xml);;All Files (*)')
if not path:
return
try:
self._xml = VariablesXML(path)
self.flat_vars = {v['name']: v for v in self._xml.flattened()}
# Получаем сырые данные по переменным
self._all_available_vars = self._xml.get_all_vars_data()
except Exception as e:
QMessageBox.critical(self, 'Parse error', f'Ошибка парсинга:\n{e}')
return
self.lbl_file.setText(path)
self.lbl_timestamp.setText(f'Timestamp: {self._xml.timestamp or "-"}')
self._populate_internal_maps_from_all_vars()
self._apply_timestamp_to_date()
self.xmlLoaded.emit(path)
self._log(f'Loaded {path}, variables={len(self._all_available_vars)})')
def _apply_timestamp_to_date(self):
if not (self._xml and self._xml.timestamp):
return
try:
# Пример: "Sat Jul 19 15:27:59 2025"
self.dt = datetime.datetime.strptime(self._xml.timestamp, "%a %b %d %H:%M:%S %Y")
except Exception as e:
print(f"Ошибка разбора timestamp '{self._xml.timestamp}': {e}")
# ------------------------------------------ Variable selector dialog ----
def _on_open_variable_selector(self):
if not self._xml:
QMessageBox.warning(self, 'No XML', 'Сначала загрузите XML файл.')
return
dialog = VariableSelectorDialog(
table=None, # не используем встроенную таблицу
all_vars=self._all_available_vars,
structs=None, # при необходимости подайте реальные структуры из XML
typedefs=None, # ...
xml_path=None, # по запросу пользователя xml_path = None
parent=self
)
if dialog.exec_() == dialog.Accepted:
# Диалог обновил self._all_available_vars напрямую
self._populate_internal_maps_from_all_vars()
self._populate_var_table()
self._log("Variable selection updated.")
# ----------------------------------------------------- Populate table ----
def _populate_var_table(self):
"""Отобразить переменные (show_var == 'true') в VariableTableWidget."""
if not self._all_available_vars:
self.var_table.setRowCount(0)
return
# Нормализуем все записи перед передачей таблице.
for var in self._all_available_vars:
self._normalize_var_record(var)
# Карта структур для автодополнения (если VariablesXML предоставляет)
try:
structs_map = self._xml.get_struct_map() if self._xml else {}
except AttributeError:
structs_map = {}
# populate() принимает: (vars_list, structs, on_change_callback)
self.var_table.populate(self._all_available_vars, structs_map, self._on_var_table_changed)
# -------------------------------------------------- Table change slot ----
def _on_var_table_changed(self, *args, **kwargs): # noqa: D401 (неиспользуемые)
"""Вызывается при любом изменении в VariableTableWidget.
Читаем данные из таблицы, мержим в self._all_available_vars (по имени),
пересобираем служебные индексы.
"""
updated = self.var_table.read_data() # list[dict]
# создаём индекс по имени из master списка
idx_by_name = {v.get('name'): v for v in self._all_available_vars if v.get('name')}
for rec in updated:
nm = rec.get('name')
if not nm:
continue
dst = idx_by_name.get(nm)
if not dst:
# Новая запись; добавляем базовые поля
dst = {
'name': nm,
'address': 0,
'file': '', 'extern': 'false', 'static': 'false',
}
self._all_available_vars.append(dst)
idx_by_name[nm] = dst
# перенести видимые поля
dst['show_var'] = str(bool(rec.get('show_var'))).lower()
dst['enable'] = str(bool(rec.get('enable'))).lower()
dst['shortname']= rec.get('shortname', nm)
dst['type'] = rec.get('type', dst.get('type',''))
# типы (строковые, с префиксами) -> нормализуем
pt_pref = rec.get('pt_type','pt_unknown') # 'pt_int16'
iq_pref = rec.get('iq_type','t_iq_none') # 't_iq10' etc.
rt_pref = rec.get('return_type', iq_pref)
self._assign_types_from_prefixed(dst, pt_pref, iq_pref, rt_pref)
# Пересобрать карты путей/адресов
self._populate_internal_maps_from_all_vars()
# --------------------------------- Normalize var record (public-ish) ----
def _normalize_var_record(self, var: Dict[str, Any]):
"""Унифицирует записи переменной.
Требуемые поля после нормализации:
var['ptr_type'] -> str (напр. 'int16')
var['ptr_type_enum'] -> int
var['iq_type'] -> str ('iq10')
var['iq_type_enum'] -> int
var['return_type'] -> str ('iq10')
var['return_type_enum']-> int
var['pt_type'] -> 'pt_<ptr_type>' (для совместимости с VariableTableWidget)
var['return_type_pref']-> 't_<return_type>' (см. ниже) # не обяз.
Дополнительно корректируем show_var/enable и адрес.
"""
# --- show_var / enable
var['show_var'] = str(var.get('show_var', 'false')).lower()
var['enable'] = str(var.get('enable', 'true')).lower()
# --- address
if not var.get('address'):
var_name = var.get('name')
# Ищем в self.flat_vars
if hasattr(self, 'flat_vars') and isinstance(self.flat_vars, dict):
flat_entry = self.flat_vars.get(var_name)
if flat_entry and 'address' in flat_entry:
var['address'] = flat_entry['address']
else:
var['address'] = 0
else:
var['address'] = 0
else:
# Нормализация адреса (если строка типа '0x1234')
try:
if isinstance(var['address'], str):
var['address'] = int(var['address'], 16)
except ValueError:
var['address'] = 0
# --- ptr_type (строка)
name = None
if isinstance(var.get('ptr_type'), str):
name = var['ptr_type']
elif isinstance(var.get('ptr_type_name'), str):
name = var['ptr_type_name']
elif isinstance(var.get('pt_type'), str):
name = var['pt_type'].replace('pt_','')
elif isinstance(var.get('ptr_type'), int):
name = PT_ENUM_NAME_FROM_VAL.get(var['ptr_type'], 'unknown')
else:
name = self._map_type_to_ptr_enum(var.get('type'))
val = PT_ENUM_VALUE.get(name, 0)
var['ptr_type'] = name
var['ptr_type_enum'] = val
var['pt_type'] = f'pt_{name}'
# ---------------------------------------------- prefixed assign helper ----
def _assign_types_from_prefixed(self, dst: Dict[str, Any], pt_pref: str, iq_pref: str, rt_pref: str):
"""Парсит строки вида 'pt_int16', 't_iq10' и записывает нормализованные поля."""
pt_name = pt_pref.replace('pt_','') if pt_pref else 'unknown'
iq_name = iq_pref
if iq_name.startswith('t_'):
iq_name = iq_name[2:]
rt_name = rt_pref
if rt_name.startswith('t_'):
rt_name = rt_name[2:]
dst['ptr_type'] = pt_name
dst['ptr_type_enum'] = PT_ENUM_VALUE.get(pt_name, 0)
dst['pt_type'] = f'pt_{pt_name}'
dst['iq_type'] = iq_name
dst['iq_type_enum'] = IQ_ENUM_VALUE.get(iq_name, 0)
dst['return_type'] = rt_name
dst['return_type_enum'] = IQ_ENUM_VALUE.get(rt_name, dst['iq_type_enum'])
dst['return_type_pref'] = f't_{rt_name}'
# ------------------------------------------ Populate internal maps ----
def _populate_internal_maps_from_all_vars(self):
self._path_info.clear()
self._addr_index.clear()
self._paths.clear()
for var in self._all_available_vars:
nm = var.get('name')
tp = var.get('type')
addr = var.get('address')
if nm is None:
continue
if addr is None:
addr = 0
var['address'] = 0
self._paths.append(nm)
self._path_info[nm] = (addr, tp)
if addr in self._addr_index:
self._addr_index[addr] = None
else:
self._addr_index[addr] = nm
# Обновим подсказки
self._hints.set_paths([(p, self._path_info[p][1]) for p in self._paths])
# -------------------------------------------------- Public helpers ----
def get_selected_variables_and_addresses(self) -> List[Dict[str, Any]]:
"""Возвращает список выбранных переменных (show_var == true) с адресами и типами.
Чтение из VariableTableWidget + подстановка адресов/прочих служебных полей
из master списка.
"""
tbl_data = self.var_table.read_data() # список dict'ов в формате VariableTableWidget
idx_by_name = {v.get('name'): v for v in self._all_available_vars if v.get('name')}
out: List[Dict[str, Any]] = []
for rec in tbl_data:
nm = rec.get('name')
if not nm:
continue
src = idx_by_name.get(nm, {})
addr = src.get('address')
if addr is None or addr == '' or addr == 0:
src['address'] = self.flat_vars.get(nm, {}).get('address', 0)
else:
# если это строка "0x..." — конвертируем в int
if isinstance(addr, str) and addr.startswith('0x'):
try:
src['address'] = int(addr, 16)
except ValueError:
src['address'] = self.flat_vars.get(nm, {}).get('address', 0)
type_str = src.get('type', rec.get('type','N/A'))
# нормализация типов
tmp = dict(src) # copy src to preserve extra fields (file, extern, ...)
self._assign_types_from_prefixed(tmp,
rec.get('pt_type','pt_unknown'),
rec.get('iq_type','t_iq_none'),
rec.get('return_type', rec.get('iq_type','t_iq_none')))
tmp['show_var'] = str(bool(rec.get('show_var'))).lower()
tmp['enable'] = str(bool(rec.get('enable'))).lower()
tmp['name'] = nm
tmp['address'] = addr
tmp['type'] = type_str
out.append(tmp)
return out
def get_datetime(self):
return self.dt
def set_variable_value(self, var_name: str, value: Any):
# 1. Обновляем master-список переменных
found = None
for var in self._all_available_vars:
if var.get('name') == var_name:
var['value'] = value
found = var
break
if not found:
# Если переменной нет в списке, можно либо проигнорировать, либо добавить.
return False
# 2. Обновляем отображение в таблице
self.var_table.populate(self._all_available_vars, {}, self._on_var_table_changed)
return True
# --------------- Address mapping / type mapping helpers ---------------
def _map_type_to_ptr_enum(self, type_str: Optional[str]) -> str:
if not type_str:
return 'unknown'
low = type_str.lower()
token = low.replace('*',' ').replace('[',' ')
return type_map.get(token, 'unknown').replace('pt_','')
# ----------------------------------------------------------- Logging --
def _log(self, msg: str):
print(f"[LowLevelSelectorWidget Log] {msg}")
# ---------------------------------------------------------------------------
# Тест‑прогоночка (ручной) --------------------------------------------------
# Запускать только вручную: python LowLevelSelectorWidget_refactored.py <xml>
# ---------------------------------------------------------------------------
# ----------------------------------------------------------- Demo window --
class _DemoWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('LowLevel Selector Demo')
self.selector = LowLevelSelectorWidget(self)
self.setCentralWidget(self.selector)
self.selector.variablePrepared.connect(self.on_var)
def on_var(self, data: dict):
print('Variable prepared ->', data)
def closeEvent(self, ev):
self.setCentralWidget(None)
super().closeEvent(ev)
# ----------------------------------------------------------------- main ---
if __name__ == '__main__':
app = QApplication(sys.argv)
w = _DemoWindow()
w.resize(640, 520)
w.show()
sys.exit(app.exec_())

1254
Src/tms_debugvar_term.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,86 +1,64 @@
import re # variable_select_widget.py
import pickle
import hashlib
from typing import List, Dict, Any, Optional
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
QWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLineEdit, QWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLineEdit,
QHeaderView, QCompleter QHeaderView, QCompleter
) )
from PySide2.QtGui import QKeyEvent from PySide2.QtGui import QKeyEvent
from PySide2.QtCore import Qt, QStringListModel from PySide2.QtCore import Qt, QStringListModel
import pickle
import time
import hashlib
from path_hints import PathHints, canonical_key, split_path_tokens
# ------------------------------------------------------------------
# utils
# ------------------------------------------------------------------
def compute_vars_hash(vars_list): def compute_vars_hash(vars_list):
return hashlib.sha1(pickle.dumps(vars_list)).hexdigest() return hashlib.sha1(pickle.dumps(vars_list)).hexdigest()
# Вспомогательные функции, которые теперь будут использоваться виджетом
def split_path(path):
"""
Разбивает путь на компоненты:
- 'foo[2].bar[1]->baz' ['foo', '[2]', 'bar', '[1]', 'baz']
Если видит '-' в конце строки (без '>' после) обрезает этот '-'
"""
tokens = []
token = ''
i = 0
length = len(path)
while i < length:
c = path[i]
# Разделители: '->' и '.'
if c == '-' and i + 1 < length and path[i:i+2] == '->':
if token:
tokens.append(token)
token = ''
i += 2
continue
elif c == '-' and i == length - 1:
# '-' на конце строки без '>' после — просто пропускаем его
i += 1
continue
elif c == '.':
if token:
tokens.append(token)
token = ''
i += 1
continue
elif c == '[':
if token:
tokens.append(token)
token = ''
idx = ''
while i < length and path[i] != ']':
idx += path[i]
i += 1
if i < length and path[i] == ']':
idx += ']'
i += 1
tokens.append(idx)
continue
else:
token += c
i += 1
if token:
tokens.append(token)
return tokens
def is_lazy_item(item: QTreeWidgetItem) -> bool:
def is_lazy_item(item):
return item.childCount() == 1 and item.child(0).text(0) == 'lazy_marker' return item.childCount() == 1 and item.child(0).text(0) == 'lazy_marker'
# ------------------------------------------------------------------
# VariableSelectWidget
# ------------------------------------------------------------------
class VariableSelectWidget(QWidget): class VariableSelectWidget(QWidget):
"""
Виджет выбора переменных с деревом + строкой поиска + автодополнением.
Подсказки полностью через PathHints.
ВАЖНО: ожидается, что в данных (vars_list) каждое var['name'] ПОЛНЫЙ ПУТЬ
(например: 'project.adc.status'), даже внутри children.
"""
ROLE_NAME = Qt.UserRole # локальный хвост (display)
ROLE_VAR_DICT = Qt.UserRole + 100 # исходный dict
ROLE_FULLPATH = Qt.UserRole + 200 # полный путь
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.expanded_vars = []
self.node_index = {}
self.is_autocomplete_on = True # <--- ДОБАВИТЬ ЭТУ СТРОКУ
self._bckspc_pressed = False
self.manual_completion_active = False
self._vars_hash = None
# --- UI Элементы --- # данные
self.expanded_vars: List[Dict[str, Any]] = []
self.is_autocomplete_on = True
self.manual_completion_active = False
self._bckspc_pressed = False
self._vars_hash: Optional[str] = None
# индекс: canonical_full_path -> item
self._item_by_canon: Dict[str, QTreeWidgetItem] = {}
# подсказки
self.hints = PathHints()
# --- UI ---
self.search_input = QLineEdit(self) self.search_input = QLineEdit(self)
self.search_input.setPlaceholderText("Поиск...") self.search_input.setPlaceholderText("Поиск...")
self.tree = QTreeWidget(self) self.tree = QTreeWidget(self)
self.tree.setHeaderLabels(["Имя переменной", "Тип"]) self.tree.setHeaderLabels(["Имя переменной", "Тип"])
self.tree.setSelectionMode(QTreeWidget.ExtendedSelection) self.tree.setSelectionMode(QTreeWidget.ExtendedSelection)
@ -97,32 +75,58 @@ class VariableSelectWidget(QWidget):
self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setCaseSensitivity(Qt.CaseInsensitive)
self.completer.setFilterMode(Qt.MatchContains) self.completer.setFilterMode(Qt.MatchContains)
self.completer.setWidget(self.search_input) self.completer.setWidget(self.search_input)
self.completer.activated[str].connect(self.insert_completion)
# --- Layout --- # layout
layout = QVBoxLayout(self) lay = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0) lay.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.search_input) lay.addWidget(self.search_input)
layout.addWidget(self.tree) lay.addWidget(self.tree)
# --- Соединения --- # signals
#self.search_input.textChanged.connect(self.on_search_text_changed) self.search_input.textChanged.connect(self.on_search_text_changed)
self.search_input.textChanged.connect(lambda text: self.on_search_text_changed(text))
self.search_input.installEventFilter(self) self.search_input.installEventFilter(self)
self.completer.activated[str].connect(lambda text: self.insert_completion(text))
# --- Публичные методы для управления виджетом снаружи --- # ------------------------------------------------------------------
# public api
# ------------------------------------------------------------------
def set_autocomplete(self, enabled: bool): def set_autocomplete(self, enabled: bool):
"""Включает или выключает режим автодополнения."""
self.is_autocomplete_on = enabled self.is_autocomplete_on = enabled
def set_data(self, vars_list): def set_data(self, vars_list: List[Dict[str, Any]]):
"""Основной метод для загрузки данных в виджет.""" """
Загружаем список переменных (формат: см. класс docstring).
"""
# deepcopy
self.expanded_vars = pickle.loads(pickle.dumps(vars_list, protocol=pickle.HIGHEST_PROTOCOL)) self.expanded_vars = pickle.loads(pickle.dumps(vars_list, protocol=pickle.HIGHEST_PROTOCOL))
# self.build_completion_list() # Если нужна полная перестройка списка
self.populate_tree()
# rebuild hints из полного списка узлов (каждый узел уже с full_path)
self._rebuild_hints_from_vars(self.expanded_vars)
# rebuild tree
self.populate_tree(self.expanded_vars)
# ------------------------------------------------------------------
# hints builder: дети уже содержат ПОЛНЫЙ ПУТЬ
# ------------------------------------------------------------------
def _rebuild_hints_from_vars(self, vars_list: List[Dict[str, Any]]):
paths: List[tuple] = []
def walk(node: Dict[str, Any]):
full = node.get('name', '')
if full:
paths.append((full, node.get('type')))
for ch in node.get('children', []) or []:
walk(ch)
for v in vars_list:
walk(v)
self.hints.set_paths(paths)
# ------------------------------------------------------------------
# tree building
# ------------------------------------------------------------------
def populate_tree(self, vars_list=None): def populate_tree(self, vars_list=None):
if vars_list is None: if vars_list is None:
vars_list = self.expanded_vars vars_list = self.expanded_vars
@ -130,477 +134,295 @@ class VariableSelectWidget(QWidget):
new_hash = compute_vars_hash(vars_list) new_hash = compute_vars_hash(vars_list)
if self._vars_hash == new_hash: if self._vars_hash == new_hash:
return return
self._vars_hash = new_hash self._vars_hash = new_hash
self.tree.setUpdatesEnabled(False) self.tree.setUpdatesEnabled(False)
self.tree.blockSignals(True) self.tree.blockSignals(True)
self.tree.clear() self.tree.clear()
self.node_index.clear() self._item_by_canon.clear()
for var in vars_list: # построим top-level из входного списка: определяем по глубине токенов
self.add_tree_item_lazy(None, var) # (vars_list может содержать и глубокие узлы; выберем корни = те, чей full_path не имеет родителя в списке)
full_to_node = {v['name']: v for v in vars_list}
# но safer: просто добавляем все как top-level, если ты уже передаёшь только корни.
# Если в твоих данных vars_list == корни, просто сделаем:
for v in vars_list:
self._add_tree_item_lazy(None, v)
self.tree.setUpdatesEnabled(True) self.tree.setUpdatesEnabled(True)
self.tree.blockSignals(False) self.tree.blockSignals(False)
header = self.tree.header() header = self.tree.header()
header.setSectionResizeMode(QHeaderView.Interactive) header.setSectionResizeMode(QHeaderView.Interactive)
header.setSectionResizeMode(1, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.Stretch)
self.tree.setColumnWidth(0, 400) self.tree.setColumnWidth(0, 400)
def on_item_expanded(self, item): def on_item_expanded(self, item: QTreeWidgetItem):
if is_lazy_item(item): if is_lazy_item(item):
item.removeChild(item.child(0)) item.removeChild(item.child(0))
var = item.data(0, Qt.UserRole + 100) var = item.data(0, self.ROLE_VAR_DICT)
if var: if var:
for child_var in var.get('children', []): for ch in var.get('children', []) or []:
self.add_tree_item_lazy(item, child_var) self._add_tree_item_lazy(item, ch)
# ------------------------------------------------------------------
def get_full_item_name(self, item): # item creation (var['name'] — ПОЛНЫЙ ПУТЬ)
fullname = item.text(0) # ------------------------------------------------------------------
# Заменяем '->' на '.' def _add_tree_item_lazy(self, parent: Optional[QTreeWidgetItem], var: Dict[str, Any]):
fullname = fullname.replace('->', '.') full_path = var.get('name', '')
fullname = fullname.replace('[', '.[')
return fullname
def add_tree_item_lazy(self, parent, var):
name = var['name']
type_str = var.get('type', '') type_str = var.get('type', '')
item = QTreeWidgetItem([name, type_str])
item.setData(0, Qt.UserRole, name) # здесь оставляем полный путь для отображения
full_name = self.get_full_item_name(item) item = QTreeWidgetItem([full_path, type_str])
self.node_index[full_name.lower()] = item item.setData(0, self.ROLE_NAME, full_path) # теперь ROLE_NAME = полный путь
item.setData(0, self.ROLE_VAR_DICT, var)
item.setData(0, self.ROLE_FULLPATH, full_path)
if "(bitfield:" in type_str: if "(bitfield:" in type_str:
item.setDisabled(True) item.setDisabled(True)
self.set_tool(item, "Битовые поля недоступны для выбора") self._set_tool(item, "Битовые поля недоступны для выбора")
# метаданные
for i, attr in enumerate(['file', 'extern', 'static']): for i, attr in enumerate(['file', 'extern', 'static']):
item.setData(0, Qt.UserRole + 1 + i, var.get(attr)) item.setData(0, Qt.UserRole + 1 + i, var.get(attr))
# в дерево
if parent is None: if parent is None:
self.tree.addTopLevelItem(item) self.tree.addTopLevelItem(item)
else: else:
parent.addChild(item) parent.addChild(item)
# Если есть дети — добавляем заглушку (чтобы можно было раскрыть) # lazy children
if var.get('children'): if var.get('children'):
dummy = QTreeWidgetItem(["lazy_marker"]) dummy = QTreeWidgetItem(["lazy_marker"])
item.addChild(dummy) item.addChild(dummy)
# Кэшируем детей для подгрузки по событию # индекс
item.setData(0, Qt.UserRole + 100, var) # Сохраняем var целиком self._item_by_canon[canonical_key(full_path)] = item
@staticmethod
def show_matching_path(self, item, path_parts, level=0): def _tail_token(full_path: str) -> str:
node_name = item.text(0).lower() toks = split_path_tokens(full_path)
node_parts = split_path(node_name) return toks[-1] if toks else full_path
if 'project' in node_name: # ------------------------------------------------------------------
a = 1 # filtering
# ------------------------------------------------------------------
def filter_tree(self):
"""
Быстрый фильтр:
- без разделителей substring по ЛОКАЛЬНОМУ имени top-level
- с разделителями структурный (по токенам full_path)
"""
text = (self.search_input.text() or '').strip()
low = text.lower()
parts = split_path_tokens(low) if low else []
# простой режим (нет ., ->, [):
if low and all(x not in low for x in ('.', '->', '[')):
for i in range(self.tree.topLevelItemCount()):
it = self.tree.topLevelItem(i)
full = (it.data(0, self.ROLE_FULLPATH) or '').lower()
it.setHidden(low not in full)
return
# структурный
for i in range(self.tree.topLevelItemCount()):
it = self.tree.topLevelItem(i)
self._show_matching_path(it, parts, 0)
def _show_matching_path(self, item: QTreeWidgetItem, path_parts: List[str], level: int = 0):
"""
Сравниваем введённый путь (разбитый на токены) с ПОЛНЫМ ПУТЁМ узла.
Алгоритм: берём полный путь узла, разбиваем в токены, берём уровень level,
и сравниваем с соответствующим токеном path_parts[level].
"""
full = (item.data(0, self.ROLE_FULLPATH) or '').lower()
node_parts = split_path_tokens(full)
if level >= len(path_parts): if level >= len(path_parts):
# Путь полностью пройден — показываем только этот узел (без раскрытия всех детей)
item.setHidden(False) item.setHidden(False)
item.setExpanded(False) item.setExpanded(False)
return True return True
if level >= len(node_parts): if level >= len(node_parts):
# Уровень поиска больше длины пути узла — скрываем item.setHidden(True)
item.setHidden(False) return False
search_part = path_parts[level] search_part = path_parts[level]
node_part = node_parts[level] node_part = node_parts[level]
if search_part == node_part: if search_part == node_part:
# Точное совпадение — показываем узел, идём вглубь только по совпадениям
item.setHidden(False) item.setHidden(False)
matched_any = False matched_any = False
self.on_item_expanded(item) self.on_item_expanded(item)
for i in range(item.childCount()): for i in range(item.childCount()):
child = item.child(i) ch = item.child(i)
if self.show_matching_path(child, path_parts, level + 1): if self._show_matching_path(ch, path_parts, level + 1):
matched_any = True matched_any = True
item.setExpanded(matched_any) item.setExpanded(matched_any)
return matched_any or item.childCount() == 0 return matched_any or item.childCount() == 0
elif node_part.startswith(search_part): elif node_part.startswith(search_part):
# Неполное совпадение — показываем только этот узел, детей скрываем, не раскрываем
item.setHidden(False) item.setHidden(False)
item.setExpanded(False) item.setExpanded(False)
return True return True
elif search_part in node_part and (level == len(path_parts)-1): elif search_part in node_part and (level == len(path_parts) - 1):
# Неполное совпадение — показываем только этот узел, детей скрываем, не раскрываем
item.setHidden(False) item.setHidden(False)
item.setExpanded(False) item.setExpanded(False)
return True return True
else: else:
# Несовпадение — скрываем
item.setHidden(True) item.setHidden(True)
return False return False
# ------------------------------------------------------------------
def filter_tree(self): # completions (ONLY PathHints)
text = self.search_input.text().strip().lower() # ------------------------------------------------------------------
path_parts = split_path(text) if text else [] def update_completions(self, text: Optional[str] = None) -> List[str]:
if '.' not in text and '->' not in text and '[' not in text and text != '':
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
name = item.text(0).lower()
if text in name:
item.setHidden(False)
# Не сбрасываем expanded, чтобы можно было раскрывать вручную
else:
item.setHidden(True)
else:
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
self.show_matching_path(item, path_parts, 0)
def find_node_by_path(self, root_vars, path_list):
current_level = root_vars
node = None
for part in path_list:
node = None
for var in current_level:
if var['name'] == part:
node = var
break
if node is None:
return None
current_level = node.get('children', [])
return node
def update_completions(self, text=None):
if text is None: if text is None:
text = self.search_input.text().strip() text = self.search_input.text()
suggestions = self.hints.suggest(text)
self.completer.setModel(QStringListModel(suggestions))
if suggestions:
self.completer.complete()
else: else:
text = text.strip() self.completer.popup().hide()
return suggestions
normalized_text = text.replace('->', '.') def insert_completion(self, full_path: str):
parts = split_path(text) text = self.hints.add_separator(full_path)
path_parts = parts[:-1] if parts else [] if not self._bckspc_pressed:
prefix = parts[-1].lower() if parts else ''
ends_with_sep = text.endswith('.') or text.endswith('->') or text.endswith('[')
is_index_suggestion = text.endswith('[')
completions = []
def find_exact_node(parts):
if not parts:
return None
fullname = parts[0]
for p in parts[1:]:
fullname += '.' + p
return self.node_index.get(fullname.lower())
if is_index_suggestion:
base_text = text[:-1] # убираем '['
parent_node = self.find_node_by_fullname(base_text)
if not parent_node:
base_text_clean = re.sub(r'\[\d+\]$', '', base_text)
parent_node = self.find_node_by_fullname(base_text_clean)
if parent_node:
seen = set()
for i in range(parent_node.childCount()):
child = parent_node.child(i)
if child.isHidden():
continue
cname = child.text(0)
m = re.match(rf'^{re.escape(base_text)}\[(\d+)\]$', cname)
if m and cname not in seen:
completions.append(cname)
seen.add(cname)
self.completer.setModel(QStringListModel(completions))
return completions
if ends_with_sep:
node = self.find_node_by_fullname(text[:-1])
if node:
for i in range(node.childCount()):
child = node.child(i)
if child.isHidden():
continue
completions.append(child.text(0))
elif not path_parts:
# Первый уровень — только если имя начинается с prefix
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
if item.isHidden():
continue
name = item.text(0)
if name.lower().startswith(prefix):
completions.append(name)
else:
node = find_exact_node(path_parts)
if node:
for i in range(node.childCount()):
child = node.child(i)
if child.isHidden():
continue
name = child.text(0)
name_parts = child.data(0, Qt.UserRole + 10)
if name_parts is None:
name_parts = split_path(name)
child.setData(0, Qt.UserRole + 10, name_parts)
if not name_parts:
continue
last_part = name_parts[-1].lower()
if prefix == '' or prefix in last_part: # ← строго startswith
completions.append(name)
self.completer.setModel(QStringListModel(completions))
self.completer.complete()
return completions
# Функция для поиска узла с полным именем
def find_node_by_fullname(self, name):
if name is None:
return None
normalized_name = name.replace('->', '.').lower()
normalized_name = normalized_name.replace('[', '.[').lower()
return self.node_index.get(normalized_name)
def insert_completion(self, text):
node = self.find_node_by_fullname(text)
if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->') or text.endswith('[')):
# Определяем разделитель по имени первого ребёнка
child_name = node.child(0).text(0)
if child_name.startswith(text + '->'):
text += '->'
elif child_name.startswith(text + '.'):
text += '.'
elif '[' in child_name:
text += '[' # для массивов
else:
text += '.' # fallback
if not self._bckspc_pressed:
self.search_input.setText(text)
self.search_input.setCursorPosition(len(text))
self.run_completions(text)
else:
self.search_input.setText(text) self.search_input.setText(text)
self.search_input.setCursorPosition(len(text)) self.search_input.setCursorPosition(len(text))
self.run_completions(text)
# ------------------------------------------------------------------
# events
# ------------------------------------------------------------------
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
if obj == self.search_input and isinstance(event, QKeyEvent): if obj == self.search_input and isinstance(event, QKeyEvent):
if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier: if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier:
self.manual_completion_active = True self.manual_completion_active = True
text = self.search_input.text().strip() self.run_completions(self.search_input.text())
self.run_completions(text)
elif event.key() == Qt.Key_Escape: elif event.key() == Qt.Key_Escape:
# Esc — выключаем ручной режим и скрываем подсказки, если autocomplete выключен
if not self.is_autocomplete_on: if not self.is_autocomplete_on:
self.manual_completion_active = False self.manual_completion_active = False
self.completer.popup().hide() self.completer.popup().hide()
return True return True
if event.key() == Qt.Key_Backspace: if event.key() == Qt.Key_Backspace:
self._bckspc_pressed = True self._bckspc_pressed = True
else: else:
self._bckspc_pressed = False self._bckspc_pressed = False
return super().eventFilter(obj, event) return super().eventFilter(obj, event)
def run_completions(self, text):
completions = self.update_completions(text)
if not self.is_autocomplete_on and self._bckspc_pressed: def run_completions(self, text: str):
text = text[:-1] if not self.is_autocomplete_on and not self.manual_completion_active:
self.completer.popup().hide()
if len(completions) == 1 and completions[0].lower() == text.lower(): return
# Найдем узел с таким именем self.update_completions(text)
def find_exact_item(name):
stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
while stack:
node = stack.pop()
if node.text(0).lower() == name.lower():
return node
for i in range(node.childCount()):
stack.append(node.child(i))
return None
node = find_exact_item(completions[0]) def on_search_text_changed(self, text: str):
if node and node.childCount() > 0: self.completer.setWidget(self.search_input)
# Используем первую подсказку, чтобы определить нужный разделитель
completions = self.update_completions(text + '.')
if not completions:
return
suggestion = completions[0]
# Ищем, какой символ идёт после текущего текста
separator = '.'
if suggestion.startswith(text):
rest = suggestion[len(text):]
if rest.startswith(text + '->'):
separator += '->'
elif rest.startswith(text + '.'):
separator += '.'
elif '[' in rest:
separator += '[' # для массивов
else:
separator += '.' # fallback
if not self._bckspc_pressed:
self.search_input.setText(text + separator)
completions = self.update_completions(text)
self.completer.setModel(QStringListModel(completions))
self.completer.complete()
return True
# Иначе просто показываем подсказки
self.completer.setModel(QStringListModel(completions))
if completions:
self.completer.complete()
return True
def on_search_text_changed(self, text):
sender_widget = self.sender()
sender_name = sender_widget.objectName() if sender_widget else "Unknown Sender"
self.completer.setWidget(self.search_input)
self.filter_tree() self.filter_tree()
if text == None: if text is None:
text = self.search_input.text().strip() text = self.search_input.text()
if self.is_autocomplete_on: if self.is_autocomplete_on:
self.run_completions(text) self.run_completions(text)
else: else:
# Если выключено, показываем подсказки только если флаг ручного вызова True
if self.manual_completion_active: if self.manual_completion_active:
self.run_completions(text) self.run_completions(text)
else: else:
self.completer.popup().hide() self.completer.popup().hide()
def focusInEvent(self, event): def focusInEvent(self, event):
if self.completer.widget() != self.search_input: if self.completer.widget() != self.search_input:
self.completer.setWidget(self.search_input) self.completer.setWidget(self.search_input)
super().focusInEvent(event) super().focusInEvent(event)
def _custom_focus_in_event(self, event):
# Принудительно установить виджет для completer при получении фокуса
if self.completer.widget() != self.search_input:
self.completer.setWidget(self.search_input)
super(QLineEdit, self.search_input).focusInEvent(event) # Вызвать оригинальный обработчик
def build_completion_list(self):
completions = []
def recurse(var, prefix=''):
fullname = f"{prefix}.{var['name']}" if prefix else var['name']
completions.append(fullname)
for child in var.get('children', []):
recurse(child, fullname)
for v in self.expanded_vars:
recurse(v)
self.all_completions = completions
def set_tool(self, item, text):
item.setToolTip(0, text)
item.setToolTip(1, text)
def get_all_items(self):
"""Возвращает все конечные (leaf) элементы, исключая битовые поля и элементы с детьми (реальными)."""
def collect_leaf_items(parent):
leaf_items = []
for i in range(parent.childCount()):
child = parent.child(i)
if child.isHidden():
continue
# Если есть заглушка — раскрываем
self.on_item_expanded(child)
if child.childCount() == 0:
item_type = child.text(1)
if item_type and 'bitfield' in str(item_type).lower():
continue
leaf_items.append(child)
else:
leaf_items.extend(collect_leaf_items(child))
return leaf_items
all_leaf_items = []
for i in range(self.tree.topLevelItemCount()):
top = self.tree.topLevelItem(i)
# Раскрываем lazy, если надо
self.on_item_expanded(top)
if top.childCount() == 0:
item_type = top.text(1)
if item_type and 'bitfield' in str(item_type).lower():
continue
all_leaf_items.append(top)
else:
all_leaf_items.extend(collect_leaf_items(top))
return all_leaf_items
def _get_internal_selected_items(self):
"""Возвращает выделенные элементы и всех их потомков, включая lazy."""
selected = self.tree.selectedItems()
all_items = []
def collect_children(item):
# Раскрываем при необходимости
# Раскрываем lazy, если надо
self.on_item_expanded(item)
items = [item]
for i in range(item.childCount()):
child = item.child(i)
items.extend(collect_children(child))
return items
for item in selected:
all_items.extend(collect_children(item))
return all_items
def get_selected_items(self):
"""Возвращает только конечные (leaf) выделенные элементы, исключая bitfield."""
selected = self.tree.selectedItems()
leaf_items = []
for item in selected:
# Раскрываем lazy, если надо
self.on_item_expanded(item)
# Если у узла нет видимых/выделенных детей — он лист
if all(item.child(i).isHidden() or not item.child(i).isSelected() for i in range(item.childCount())):
item_type = item.data(0, Qt.UserRole)
if item_type and 'bitfield' in str(item_type).lower():
continue
leaf_items.append(item)
return leaf_items
def get_all_var_names(self):
"""Возвращает имена всех конечных (leaf) переменных, исключая битовые поля и группы."""
return [item.text(0) for item in self.get_all_items() if item.text(0)]
def _get_internal_selected_var_names(self):
"""Возвращает имена выделенных переменных."""
return [item.text(0) for item in self._get_internal_selected_items() if item.text(0)]
def get_selected_var_names(self):
"""Возвращает имена только конечных (leaf) переменных из выделенных."""
return [item.text(0) for item in self.get_selected_items() if item.text(0)]
def closeEvent(self, event): def closeEvent(self, event):
self.completer.setWidget(None) self.completer.setWidget(None)
self.completer.deleteLater() self.completer.deleteLater()
super().closeEvent(event) super().closeEvent(event)
# ------------------------------------------------------------------
# lookup by full path
# ------------------------------------------------------------------
def find_item_by_fullpath(self, path: str) -> Optional[QTreeWidgetItem]:
return self._item_by_canon.get(canonical_key(path))
# ------------------------------------------------------------------
# tooltips
# ------------------------------------------------------------------
def _set_tool(self, item: QTreeWidgetItem, text: str):
item.setToolTip(0, text)
item.setToolTip(1, text)
# ------------------------------------------------------------------
# selection helpers
# ------------------------------------------------------------------
def get_all_items(self):
"""Все leaf-узлы (подгружаем lazy)."""
def collect_leaf(parent):
leaves = []
for i in range(parent.childCount()):
ch = parent.child(i)
if ch.isHidden():
continue
self.on_item_expanded(ch)
if ch.childCount() == 0:
t = ch.text(1)
if t and 'bitfield' in t.lower():
continue
leaves.append(ch)
else:
leaves.extend(collect_leaf(ch))
return leaves
out = []
for i in range(self.tree.topLevelItemCount()):
top = self.tree.topLevelItem(i)
self.on_item_expanded(top)
if top.childCount() == 0:
t = top.text(1)
if t and 'bitfield' in t.lower():
continue
out.append(top)
else:
out.extend(collect_leaf(top))
return out
def _get_internal_selected_items(self):
selected = self.tree.selectedItems()
all_items = []
def collect(item):
self.on_item_expanded(item)
res = [item]
for i in range(item.childCount()):
res.extend(collect(item.child(i)))
return res
for it in selected:
all_items.extend(collect(it))
return all_items
def get_selected_items(self):
selected = self.tree.selectedItems()
leaves = []
for it in selected:
self.on_item_expanded(it)
if all(it.child(i).isHidden() or not it.child(i).isSelected() for i in range(it.childCount())):
t = it.data(0, self.ROLE_NAME)
if t and isinstance(t, str) and 'bitfield' in t.lower():
continue
leaves.append(it)
return leaves
def get_all_var_names(self):
return [it.data(0, self.ROLE_FULLPATH) for it in self.get_all_items() if it.data(0, self.ROLE_FULLPATH)]
def _get_internal_selected_var_names(self):
return [it.data(0, self.ROLE_FULLPATH) for it in self._get_internal_selected_items() if it.data(0, self.ROLE_FULLPATH)]
def get_selected_var_names(self):
return [it.data(0, self.ROLE_FULLPATH) for it in self.get_selected_items() if it.data(0, self.ROLE_FULLPATH)]

View File

@ -304,7 +304,7 @@ class VariableSelectorDialog(QDialog):
# Проверка пути к XML # Проверка пути к XML
if not hasattr(self, 'xml_path') or not self.xml_path: if not hasattr(self, 'xml_path') or not self.xml_path:
from PySide2.QtWidgets import QMessageBox from PySide2.QtWidgets import QMessageBox
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.") #QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
return return
root, tree = myXML.safe_parse_xml(self.xml_path) root, tree = myXML.safe_parse_xml(self.xml_path)

View File

@ -1,10 +1,10 @@
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter, QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter,
QAbstractItemView, QHeaderView, QLabel, QAbstractItemView, QHeaderView, QLabel, QSpacerItem, QSizePolicy, QSpinBox,
QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QScrollArea, QWidget QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QScrollArea, QWidget
) )
from PySide2.QtGui import QColor, QBrush, QPalette from PySide2.QtGui import QColor, QBrush, QPalette
from PySide2.QtCore import Qt from PySide2.QtCore import Qt, QSettings
from enum import IntEnum from enum import IntEnum
from generate_debug_vars import type_map from generate_debug_vars import type_map
import time import time
@ -60,6 +60,57 @@ class FilterDialog(QDialog):
return [cb.text() for cb in self.checkboxes if cb.isChecked()] return [cb.text() for cb in self.checkboxes if cb.isChecked()]
class SetSizeDialog(QDialog):
"""
Диалоговое окно для выбора числового значения (размера).
"""
def __init__(self, parent=None, initial_value=10, min_value=1, max_value=50, title="Укажите размер короткого имени"):
super().__init__(parent)
self.setWindowTitle(title)
self.setFixedSize(320, 120) # Задаем фиксированный размер для аккуратного вида
# Основной вертикальный макет
main_layout = QVBoxLayout(self)
# Макет для ввода значения
input_layout = QHBoxLayout()
label = QLabel("Количество символов:", self)
self.spin_box = QSpinBox(self)
self.spin_box.setRange(min_value, max_value) # Устанавливаем диапазон допустимых значений
initial_value = parent._shortname_size
self.spin_box.setValue(initial_value) # Устанавливаем начальное значение
self.spin_box.setFocus() # Устанавливаем фокус на поле ввода
input_layout.addWidget(label)
input_layout.addWidget(self.spin_box)
main_layout.addLayout(input_layout)
# Добавляем пустое пространство для лучшего разделения
main_layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding))
# Макет для кнопок
btn_layout = QHBoxLayout()
btn_layout.addStretch() # Добавляем растягивающийся элемент, чтобы кнопки были справа
btn_ok = QPushButton("OK")
btn_cancel = QPushButton("Отмена")
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_cancel)
main_layout.addLayout(btn_layout)
# Подключение сигналов к слотам
btn_ok.clicked.connect(self.accept) # При нажатии "OK" диалог закроется со статусом "Accepted"
btn_cancel.clicked.connect(self.reject) # При нажатии "Отмена" - со статусом "Rejected"
def get_selected_size(self):
"""
Возвращает значение, выбранное в QSpinBox.
"""
return self.spin_box.value()
class CtrlScrollComboBox(QComboBox): class CtrlScrollComboBox(QComboBox):
def wheelEvent(self, event): def wheelEvent(self, event):
if event.modifiers() & Qt.ControlModifier: if event.modifiers() & Qt.ControlModifier:
@ -68,79 +119,89 @@ class CtrlScrollComboBox(QComboBox):
event.ignore() event.ignore()
class VariableTableWidget(QTableWidget): class VariableTableWidget(QTableWidget):
def __init__(self, parent=None): def __init__(self, parent=None, show_value_instead_of_shortname=0):
super().__init__(0, 8, parent)
# Таблица переменных # Таблица переменных
self.setHorizontalHeaderLabels([ if show_value_instead_of_shortname:
'', # новый столбец super().__init__(0, 8, parent)
'En', self.setHorizontalHeaderLabels([
'Name', '',
'Origin Type', 'En',
'Base Type', 'Name',
'IQ Type', 'Origin Type',
'Return Type', 'Base Type',
'Short Name' 'IQ Type',
]) 'Return Type',
'Value'
])
self._show_value = True
else:
super().__init__(0, 8, parent)
self.setHorizontalHeaderLabels([
'',
'En',
'Name',
'Origin Type',
'Base Type',
'IQ Type',
'Return Type',
'Short Name'
])
self._show_value = False
self.setEditTriggers(QAbstractItemView.AllEditTriggers) self.setEditTriggers(QAbstractItemView.AllEditTriggers)
self.var_list = [] self.var_list = []
# QSettings
self.settings = QSettings("SET", "DebugVarEdit_VarTable")
shortsize = self.settings.value("shortname_size", True, type=int)
self._shortname_size = shortsize
self.type_options = list(dict.fromkeys(type_map.values())) self.type_options = list(dict.fromkeys(type_map.values()))
self.pt_types_all = [t.replace('pt_', '') for t in self.type_options] self.pt_types_all = [t.replace('pt_', '') for t in self.type_options]
self.iq_types_all = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)] self.iq_types_all = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
# Задаём базовые iq-типы (без префикса 'iq_')
self.iq_types = ['iq_none', 'iq', 'iq10', 'iq15', 'iq19', 'iq24'] self.iq_types = ['iq_none', 'iq', 'iq10', 'iq15', 'iq19', 'iq24']
# Фильтруем типы из type_map.values() исключая те, что содержат 'arr' или 'ptr' type_options = [t for t in dict.fromkeys(type_map.values()) if 'arr' not in t and 'ptr' not in t
type_options = [t for t in dict.fromkeys(type_map.values()) if 'arr' not in t and 'ptr' not in t and and 'struct' not in t and 'union' not in t and '64' not in t]
'struct' not in t and 'union' not in t and '64' not in t]
# Формируем display_type_options без префикса 'pt_'
self.pt_types = [t.replace('pt_', '') for t in type_options] self.pt_types = [t.replace('pt_', '') for t in type_options]
self._iq_type_filter = list(self.iq_types) # Текущий фильтр iq типов (по умолчанию все) self._iq_type_filter = list(self.iq_types)
self._pt_type_filter = list(self.pt_types) self._pt_type_filter = list(self.pt_types)
self._ret_type_filter = list(self.iq_types) self._ret_type_filter = list(self.iq_types)
header = self.horizontalHeader()
# Для остальных колонок — растяжение (Stretch), чтобы они заняли всю оставшуюся ширину
header = self.horizontalHeader()
for col in range(self.columnCount()): for col in range(self.columnCount()):
if col == self.columnCount() - 1: if col == self.columnCount() - 1:
header.setSectionResizeMode(col, QHeaderView.Stretch) header.setSectionResizeMode(col, QHeaderView.Stretch)
else: else:
header.setSectionResizeMode(col, QHeaderView.Interactive) header.setSectionResizeMode(col, QHeaderView.Interactive)
parent_widget = self.parentWidget()
# Сделаем колонки с номерами фиксированной ширины
self.setColumnWidth(rows.No, 30) self.setColumnWidth(rows.No, 30)
self.setColumnWidth(rows.include, 30) self.setColumnWidth(rows.include, 30)
self.setColumnWidth(rows.pt_type, 85) self.setColumnWidth(rows.pt_type, 85)
self.setColumnWidth(rows.iq_type, 85) self.setColumnWidth(rows.iq_type, 85)
self.setColumnWidth(rows.ret_type, 85) self.setColumnWidth(rows.ret_type, 85)
self.setColumnWidth(rows.name, 300) self.setColumnWidth(rows.name, 300)
self.setColumnWidth(rows.type, 100) self.setColumnWidth(rows.type, 100)
self._resizing = False self._resizing = False
self.horizontalHeader().sectionResized.connect(self.on_section_resized) self.horizontalHeader().sectionResized.connect(self.on_section_resized)
self.horizontalHeader().sectionClicked.connect(self.on_header_clicked) self.horizontalHeader().sectionClicked.connect(self.on_header_clicked)
def populate(self, vars_list, structs, on_change_callback): def populate(self, vars_list, structs, on_change_callback):
self.var_list = vars_list self.var_list = vars_list
self.setUpdatesEnabled(False)
self.blockSignals(True)
# --- ДО: удаляем отображение структур и union-переменных
for var in vars_list: for var in vars_list:
pt_type = var.get('pt_type', '') pt_type = var.get('pt_type', '')
if 'struct' in pt_type or 'union' in pt_type: if 'struct' in pt_type or 'union' in pt_type:
var['show_var'] = 'false' var['show_var'] = 'false'
var['enable'] = 'false' var['enable'] = 'false'
filtered_vars = [v for v in vars_list if v.get('show_var', 'false') == 'true'] filtered_vars = [v for v in vars_list if v.get('show_var', 'false') == 'true']
self.setRowCount(len(filtered_vars)) self.setRowCount(len(filtered_vars))
self.verticalHeader().setVisible(False) self.verticalHeader().setVisible(False)
style_with_padding = "padding-left: 5px; padding-right: 5px; font-size: 14pt; font-family: 'Segoe UI';" style_with_padding = "padding-left: 5px; padding-right: 5px; font-size: 14pt; font-family: 'Segoe UI';"
for row, var in enumerate(filtered_vars): for row, var in enumerate(filtered_vars):
# № # №
no_item = QTableWidgetItem(str(row)) no_item = QTableWidgetItem(str(row))
@ -156,25 +217,21 @@ class VariableTableWidget(QTableWidget):
# Name # Name
name_edit = QLineEdit(var['name']) name_edit = QLineEdit(var['name'])
if var['type'] in structs:
completer = QCompleter(structs[var['type']].keys())
completer.setCaseSensitivity(Qt.CaseInsensitive)
name_edit.setCompleter(completer)
name_edit.textChanged.connect(on_change_callback) name_edit.textChanged.connect(on_change_callback)
name_edit.setStyleSheet(style_with_padding) name_edit.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.name, name_edit) self.setCellWidget(row, rows.name, name_edit)
# Origin Type (readonly) # Origin Type
origin_item = QTableWidgetItem(var.get('type', '')) origin_item = QTableWidgetItem(var.get('type', ''))
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
origin_item.setToolTip(var.get('type', '')) # Всплывающая подсказка origin_item.setToolTip(var.get('type', ''))
origin_item.setForeground(QBrush(Qt.black)) origin_item.setForeground(QBrush(Qt.black))
self.setItem(row, rows.type, origin_item) self.setItem(row, rows.type, origin_item)
# pt_type # pt_type
pt_combo = CtrlScrollComboBox() pt_combo = CtrlScrollComboBox()
pt_combo.addItems(self.pt_types) pt_combo.addItems(self.pt_types)
value = var['pt_type'].replace('pt_', '') value = var.get('pt_type', 'unknown').replace('pt_', '')
if value not in self.pt_types: if value not in self.pt_types:
pt_combo.addItem(value) pt_combo.addItem(value)
pt_combo.setCurrentText(value) pt_combo.setCurrentText(value)
@ -185,7 +242,7 @@ class VariableTableWidget(QTableWidget):
# iq_type # iq_type
iq_combo = CtrlScrollComboBox() iq_combo = CtrlScrollComboBox()
iq_combo.addItems(self.iq_types) iq_combo.addItems(self.iq_types)
value = var['iq_type'].replace('t_', '') value = var.get('iq_type', 'iq_none').replace('t_', '')
if value not in self.iq_types: if value not in self.iq_types:
iq_combo.addItem(value) iq_combo.addItem(value)
iq_combo.setCurrentText(value) iq_combo.setCurrentText(value)
@ -196,19 +253,32 @@ class VariableTableWidget(QTableWidget):
# return_type # return_type
ret_combo = CtrlScrollComboBox() ret_combo = CtrlScrollComboBox()
ret_combo.addItems(self.iq_types) ret_combo.addItems(self.iq_types)
value = var['return_type'].replace('t_', '') value = var.get('return_type', 'iq_none').replace('t_', '')
if value not in self.iq_types:
ret_combo.addItem(value)
ret_combo.setCurrentText(value) ret_combo.setCurrentText(value)
ret_combo.currentTextChanged.connect(on_change_callback) ret_combo.currentTextChanged.connect(on_change_callback)
ret_combo.setStyleSheet(style_with_padding) ret_combo.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.ret_type, ret_combo) self.setCellWidget(row, rows.ret_type, ret_combo)
# short_name # Последний столбец
short_name_val = var.get('shortname', var['name']) if self._show_value:
short_name_edit = QLineEdit(short_name_val) val = var.get('value', '')
short_name_edit.textChanged.connect(on_change_callback) if val is None:
short_name_edit.setStyleSheet(style_with_padding) val = ''
self.setCellWidget(row, rows.short_name, short_name_edit) val_edit = QLineEdit(str(val))
val_edit.textChanged.connect(on_change_callback)
val_edit.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.short_name, val_edit)
else:
short_name_val = var.get('shortname', var['name'])
short_name_edit = QLineEdit(short_name_val)
short_name_edit.textChanged.connect(on_change_callback)
short_name_edit.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.short_name, short_name_edit)
self.blockSignals(False)
self.setUpdatesEnabled(True)
self.check() self.check()
def check(self): def check(self):
@ -231,7 +301,7 @@ class VariableTableWidget(QTableWidget):
t3 = time.time() t3 = time.time()
shortname = short_name_edit.text() if short_name_edit else "" shortname = short_name_edit.text() if short_name_edit else ""
long_shortname = len(shortname) > 10 long_shortname = len(shortname) > self._shortname_size
found = name in var_names_set found = name in var_names_set
color = None color = None
@ -293,6 +363,13 @@ class VariableTableWidget(QTableWidget):
if dlg.exec_(): if dlg.exec_():
self._ret_type_filter = dlg.get_selected() self._ret_type_filter = dlg.get_selected()
self.update_comboboxes({rows.ret_type: self._ret_type_filter}) self.update_comboboxes({rows.ret_type: self._ret_type_filter})
elif logicalIndex == rows.short_name:
dlg = SetSizeDialog(self)
if dlg.exec_():
self._shortname_size = dlg.get_selected_size()
self.settings.setValue("shortname_size", self._shortname_size)
self.check()
@ -312,10 +389,9 @@ class VariableTableWidget(QTableWidget):
combo.clear() combo.clear()
combo.addItems(allowed_items) combo.addItems(allowed_items)
if current in allowed_items: if current not in allowed_items:
combo.setCurrentText(current) combo.addItem(current)
else: combo.setCurrentText(current)
combo.setCurrentIndex(0)
combo.blockSignals(False) combo.blockSignals(False)

View File

@ -1,134 +1,288 @@
#include "debug_tools.h" #include "debug_tools.h"
#include "IQmathLib.h"
DebugLowLevel_t debug_ll = DEBUG_LOWLEVEL_INIT; #if !defined(GLOBAL_Q)
#define GLOBAL_Q 16
#endif
DebugLowLevel_t debug_ll = DEBUG_LOWLEVEL_INIT; ///< Ñòðóêòóðà îòëàäêè íèæíåãî óðîâíÿ (èíèöèàëèçàöèÿ)
static int getDebugVar(DebugVar_t *var, int32_t *int_var, float *float_var);
static int convertDebugVarToIQx(DebugVar_t *var, int32_t *ret_var);
static int iqTypeToQ(DebugVarIQType_t t);
static int is_addr_in_allowed_ranges(uint32_t addr_val, const AddrRange_t *ranges, int count);
/**
* @brief Ìàññèâ äîïóñòèìûõ äèàïàçîíîâ àäðåñîâ äëÿ îòëàäî÷íîãî ÷òåíèÿ
*
* Âêëþ÷àåò â ñåáÿ íàáîð äèàïàçîíîâ ïàìÿòè, ðàçðåø¸ííûõ äëÿ äîñòóïà
* ôóíêöèåé Debug_LowLevel_ReadVar.
*/
static const AddrRange_t debug_allowed_ranges[] = ALLOWED_ADDRESS_RANGES;
/**
* @brief Êîëè÷åñòâî ýëåìåíòîâ â ìàññèâå debug_allowed_ranges
*/
static const int debug_allowed_ranges_count = sizeof(debug_allowed_ranges) / sizeof(debug_allowed_ranges[0]);
static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var);
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var);
///////////////////////////----EXAPLE-----////////////////////////////// ///////////////////////////----EXAPLE-----//////////////////////////////
long var_numb = 1; int var_numb = 1; ///< Ïðèìåð ïåðåìåííîé äëÿ îòëàäêè
DebugVarName_t var_name; DebugVarName_t var_name; ///< Èìÿ ïåðåìåííîé
long return_var; int32_t return_var; ///< Ïåðåìåííàÿ äëÿ âîçâðàòà ðåçóëüòàòà
long return_ll_var; int32_t return_ll_var; ///< Âîçâðàùàåìîå çíà÷åíèå ñ íèæíåãî óðîâíÿ
int result; int result; ///< Ïåðåìåííàÿ ðåçóëüòàòà
char ext_date[] = {7, 233, 11, 07, 16, 50}; DateTime_t ext_date = {2025, 11, 07, 16, 50}; ///< Ïðèìåð âíåøíåé äàòû ñáîðêè
/**
* @brief Ïðèìåð èñïîëüçîâàíèÿ ôóíêöèé îòëàäêè.
* @details Äåìîíñòðàöèîííàÿ ôóíêöèÿ äëÿ ðàáîòû ñ ïåðåìåííûìè îòëàäêè.
*/
void Debug_Test_Example(void) void Debug_Test_Example(void)
{ {
return;
result = Debug_ReadVar(var_numb, &return_var); 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) if(Debug_LowLevel_Initialize(&ext_date) == 0)
result = Debug_LowLevel_ReadVar(&return_ll_var); result = Debug_LowLevel_ReadVar(&return_ll_var);
} }
///////////////////////////----PUBLIC-----////////////////////////////// ///////////////////////////----PUBLIC-----//////////////////////////////
int Debug_ReadVar(int var_ind, long *return_long)
/**
* @brief ×èòàåò ïåðåìåííóþ ïî èíäåêñó.
* @param var_ind èíäåêñ ïåðåìåííîé.
* @param return_32b óêàçàòåëü äëÿ âîçâðàòà ðåçóëüòàòà.
* @return int 0: óñïåõ, 1: îøèáêà.
* @details Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ çíà÷åíèé ïåðåìåííûõ ïî èõ èíäåêñó.
*/
int Debug_ReadVar(int var_ind, int32_t *return_32b)
{ {
if(return_long == NULL) int32_t tmp_var;
return 1;
long tmp_var;
if(return_32b == NULL)
return DEBUG_ERR_INTERNAL;
if (var_ind >= DebugVar_Qnt) if (var_ind >= DebugVar_Qnt)
return 1; return DEBUG_ERR_VAR_NUMB;
if((dbg_vars[var_numb].ptr_type == pt_struct) || (dbg_vars[var_numb].ptr_type == pt_union) || if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) ||
(dbg_vars[var_numb].ptr_type == pt_unknown)) (dbg_vars[var_ind].ptr_type == pt_unknown))
return 1; return DEBUG_ERR_INVALID_VAR;
return convertDebugVarToIQx(&dbg_vars[var_ind], return_32b);
return convertDebugVarToIQx(&dbg_vars[var_numb], return_long);
} }
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr) /**
* @brief ×èòàåò âîçâðàùàåìûé òèï (IQ) ïåðåìåííîé ïî èíäåêñó.
* @param var_ind èíäåêñ ïåðåìåííîé.
* @param vartype óêàçàòåëü äëÿ âîçâðàòà òèïà.
* @return int 0: óñïåõ, 1: îøèáêà.
* @details Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ âîçâðàùàåìîãî òèïà (IQ) ïåðåìåííûõ ïî èõ èíäåêñó.
*/
int Debug_ReadVarReturnType(int var_ind, int *vartype)
{ {
if(name_ptr == NULL) int rettype;
return 1; if(vartype == NULL)
return DEBUG_ERR_INTERNAL;
if (var_ind >= DebugVar_Qnt) 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 DEBUG_ERR_INVALID_VAR;
int i; *vartype = iqTypeToQ(dbg_vars[var_ind].return_type);
// Êîïèðîâàíèå ñ çàùèòîé îò ïåðåïîëíåíèÿ è ÿâíîé îñòàíîâêîé ïî '\0'
for (i = 0; i < sizeof(dbg_vars[var_numb].name); i++)
{
name_ptr[i] = dbg_vars[var_numb].name[i];
if (dbg_vars[var_numb].name[i] == '\0')
break;
}
// Ãàðàíòèðîâàííîå çàâåðøåíèå ñòðîêè (íà ñëó÷àé, åñëè â var->name íå áûëî '\0')
name_ptr[sizeof(dbg_vars[var_numb].name) - 1] = '\0';
return 0; return 0;
} }
/**
* @brief ×èòàåò òèï ïåðåìåííîé ïî èíäåêñó.
int Debug_LowLevel_ReadVar(long *return_long) * @param var_ind èíäåêñ ïåðåìåííîé.
* @param vartype óêàçàòåëü äëÿ âîçâðàòà òèïà.
* @return int 0: óñïåõ, 1: îøèáêà.
* @details Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ òèïà ïåðåìåííûõ ïî èõ èíäåêñó.
*/
int Debug_ReadVarType(int var_ind, int *vartype)
{ {
if (return_long == NULL) int rettype;
return 1; if(vartype == NULL)
if (debug_ll.isVerified == 0) return DEBUG_ERR_INTERNAL;
return 1; 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;
char *addr = debug_ll.dbg_var.Ptr; *vartype = dbg_vars[var_ind].ptr_type;
unsigned long addr_val = (unsigned long)addr;
// Ðàçðåø¸ííûå äèàïàçîíû ïàìÿòè (èç .cmd ôàéëà) switch(dbg_vars[var_ind].ptr_type)
if (!( {
(addr_val <= 0x0007FF) || // RAMM0 + RAMM1 case pt_int8:
(addr_val >= 0x008120 && addr_val <= 0x009FFC) || // L0 + L1 SARAM case pt_int16:
(addr_val >= 0x3F8000 && addr_val <= 0x3F9FFF) || // PRAMH0 + DRAMH0 case pt_int32:
(addr_val >= 0x3FF000 && addr_val <= 0x3FFFFF) || // BOOTROM + RESET case pt_float:
(addr_val >= 0x080002 && addr_val <= 0x09FFFF) || // RAMEX1 *vartype = dbg_vars[var_ind].ptr_type | DEBUG_SIGNED_VAR;
(addr_val >= 0x0F0000 && addr_val <= 0x0FFEFF) || // RAMEX4 break;
(addr_val >= 0x100002 && addr_val <= 0x103FFF) || // RAMEX0 + RAMEX2 + RAMEX01
(addr_val >= 0x102000 && addr_val <= 0x103FFF) // RAMEX2 default:
)) { *vartype = dbg_vars[var_ind].ptr_type;
return 2; // Çàïðåù¸ííûé àäðåñ — íåëüçÿ ÷èòàòü break;
} }
return convertDebugVarToIQx(&debug_ll.dbg_var, return_long); return 0;
;
} }
/**
int Debug_LowLevel_Initialize(const char* external_date) * @brief ×èòàåò èìÿ ïåðåìåííîé ïî èíäåêñó.
* @param var_ind èíäåêñ ïåðåìåííîé.
* @param name_ptr óêàçàòåëü íà áóôåð èìåíè (DebugVarName_t).
* @return int 0: óñïåõ, 1: îøèáêà.
* @details Êîïèðóåò èìÿ ïåðåìåííîé â ïðåäîñòàâëåííûé áóôåð.
*/
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr, int *length)
{ {
if (external_date == NULL) { int i;
return -1;
if(name_ptr == NULL)
return DEBUG_ERR_INTERNAL;
if (var_ind >= DebugVar_Qnt)
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';
return 0;
}
/**
* @brief ×èòàåò çíà÷åíèå ïåðåìåííîé îòëàäêè ñ íèæíåãî óðîâíÿ.
* @param return_32b óêàçàòåëü íà ïåðåìåííóþ, êóäà çàïèñûâàåòñÿ ðåçóëüòàò.
* @return int 0: óñïåõ, 1: îøèáêà, 2: íåäîïóñòèìûé àäðåñ.
* @details Èñïîëüçóåò àäðåññ, ïåðåäàâàåìûé ñ òåðìèíàëêè äëÿ ïîëó÷åíèÿ çíà÷åíèÿ.
*/
int Debug_LowLevel_ReadVar(int32_t *return_32b)
{
uint8_t *addr = debug_ll.dbg_var.Ptr;
uint32_t addr_val = (uint32_t)addr;
if (return_32b == NULL)
return DEBUG_ERR_INTERNAL;
if (debug_ll.isVerified == 0)
return DEBUG_ERR_DATATIME;
if (is_addr_in_allowed_ranges(addr_val, debug_allowed_ranges, debug_allowed_ranges_count) != 0)
{
return DEBUG_ERR_ADDR; // Çàïðåù¸ííûé àäðåñ — íåëüçÿ ÷èòàòü
} }
// Ïðåîáðàçóåì external_date â ñòðóêòóðó return convertDebugVarToIQx(&debug_ll.dbg_var, return_32b);
DateTime_t ext; }
ext.year = (external_date[0] << 8) | external_date[1];
ext.day = external_date[2]; /**
ext.month = external_date[3]; * @brief Èíèöèàëèçàöèÿ îòëàäêè íà íèæíåì óðîâíå ïî äàòå ñáîðêè.
ext.hour = external_date[4]; * @param external_date ñòðóêòóðà ñ äàòîé DateTime_t
ext.minute = external_date[5]; * @return int 0: ñîâïàäàåò, 1: íå ñîâïàäàåò, -1: îøèáêà.
* @details Ñðàâíèâàåò äàòó êîìïèëÿöèè ñ çàïðàøèâàåìîé è èíèöèàëèçèðóåò îòëàäî÷íóþ ïåðåìåííóþ.
*/
int Debug_LowLevel_Initialize(DateTime_t* external_date)
{
if (external_date == NULL) {
return DEBUG_ERR_INTERNAL;
}
// Ñðàâíåíèå âñåõ ïîëåé // Ñðàâíåíèå âñåõ ïîëåé
if (ext.year == debug_ll.build_date.year && if (external_date->year == debug_ll.build_date.year &&
ext.month == debug_ll.build_date.month && external_date->month == debug_ll.build_date.month &&
ext.day == debug_ll.build_date.day && external_date->day == debug_ll.build_date.day &&
ext.hour == debug_ll.build_date.hour && external_date->hour == debug_ll.build_date.hour &&
ext.minute == debug_ll.build_date.minute) external_date->minute == debug_ll.build_date.minute)
{ {
debug_ll.isVerified = 1; debug_ll.isVerified = 1;
return 0; // Ñîâïàëî return 0; // Ñîâïàëî
} }
debug_ll.isVerified = 0; debug_ll.isVerified = 0;
return 1; // Íå ñîâïàëî return DEBUG_ERR_DATATIME; // Íå ñîâïàëî
}
/**
* @brief ×èòàåò âîçâðàùàåìûé òèï (IQ) íèçêîóðîâíåíî çàäàííîé ïåðåìåííîé.
* @param var_ind èíäåêñ ïåðåìåííîé.
* @param vartype óêàçàòåëü äëÿ âîçâðàòà òèïà.
* @return int 0: óñïåõ, 1: îøèáêà.
* @details Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ âîçâðàùàåìîãî òèïà (IQ) ïåðåìåííûõ ïî èõ èíäåêñó.
*/
int Debug_LowLevel_ReadVarReturnType(int *vartype)
{
int rettype;
if(vartype == NULL)
return DEBUG_ERR_INTERNAL;
if((debug_ll.dbg_var.ptr_type == pt_struct) || (debug_ll.dbg_var.ptr_type == pt_union) ||
(debug_ll.dbg_var.ptr_type == pt_unknown))
return DEBUG_ERR_INVALID_VAR;
*vartype = iqTypeToQ(debug_ll.dbg_var.return_type);
return 0;
}
/**
* @brief ×èòàåò òèï íèçêîóðîâíåíî çàäàííîé ïåðåìåííîé.
* @param var_ind èíäåêñ ïåðåìåííîé.
* @param vartype óêàçàòåëü äëÿ âîçâðàòà òèïà.
* @return int 0: óñïåõ, 1: îøèáêà.
*/
int Debug_LowLevel_ReadVarType(int *vartype)
{
int rettype;
if(vartype == NULL)
return DEBUG_ERR_INTERNAL;
if((debug_ll.dbg_var.ptr_type == pt_struct) || (debug_ll.dbg_var.ptr_type == pt_union) ||
(debug_ll.dbg_var.ptr_type == pt_unknown))
return DEBUG_ERR_INVALID_VAR;
*vartype = debug_ll.dbg_var.ptr_type;
switch(debug_ll.dbg_var.ptr_type)
{
case pt_int8:
case pt_int16:
case pt_int32:
case pt_float:
*vartype = debug_ll.dbg_var.ptr_type | DEBUG_SIGNED_VAR;
break;
default:
*vartype = debug_ll.dbg_var.ptr_type;
break;
}
return 0;
} }
/////////////////////----INTERNAL FUNCTIONS-----//////////////////////// /////////////////////----INTERNAL FUNCTIONS-----////////////////////////
/**
* @brief Ïðåîáðàçóåò òèï IQ ïåðåìåííîé â ÷èñëî áèòîâ äëÿ ñäâèãà(Q-ôàêòîð).
* @param t òèï IQ (ïåðå÷èñëåíèå DebugVarIQType_t).
* @return int Q-ôàêòîð (íàïðèìåð, 24), 0: åñëè t_iq_none, -1: îøèáêà.
* @details Ñîïîñòàâëÿåò òèï IQ ïåðåìåííîé ñ ñîîòâåòñòâóþùèì Q-çíà÷åíèåì.
*/
static int iqTypeToQ(DebugVarIQType_t t) static int iqTypeToQ(DebugVarIQType_t t)
{ {
if (t == t_iq_none) if (t == t_iq_none)
@ -138,47 +292,53 @@ static int iqTypeToQ(DebugVarIQType_t t)
else if (t >= t_iq1 && t <= t_iq30) else if (t >= t_iq1 && t <= t_iq30)
return (int)t - (int)t_iq1 + 1; // íàïðèìåð t_iq1 -> 1, t_iq2 -> 2 è ò.ä. return (int)t - (int)t_iq1 + 1; // íàïðèìåð t_iq1 -> 1, t_iq2 -> 2 è ò.ä.
else else
return -1; // îøèáêà return 0; // îøèáêà
} }
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var) /**
* @brief Ïðåîáðàçóåò ïåðåìåííóþ îòëàäêè â IQ ôîðìàò.
* @param var óêàçàòåëü íà ïåðåìåííóþ îòëàäêè.
* @param ret_var óêàçàòåëü äëÿ âîçâðàòà çíà÷åíèÿ â ôîðìàòå 32 áèòà.
* @return int 0: óñïåõ, 1: îøèáêà ÷òåíèÿ, 2: íåïðàâèëüíûé ôîðìàò, 3: ïåðåïîëíåíèå.
* @details Îïðåäåëÿåò ôîðìàò IQ ïåðåìåííîé, êîíâåðòèðóåò å¸ â 32b ñ ó÷¸òîì ìàñøòàáà.
*/
static int convertDebugVarToIQx(DebugVar_t *var, int32_t *ret_var)
{ {
long iq_numb, iq_united, iq_final; int32_t iq_numb, iq_united, iq_final;
int64_t iq_united64 = 0;
int64_t iq_final64 = 0;
int status;
float float_numb; float float_numb;
if(getDebugVar(var, &iq_numb, &float_numb) != 0) status = getDebugVar(var, &iq_numb, &float_numb);
return 1; if(status != 0)
return status;
int src_q = iqTypeToQ(var->iq_type); int src_q = iqTypeToQ(var->iq_type);
int dst_q = iqTypeToQ(var->return_type); int dst_q = iqTypeToQ(var->return_type);
if (src_q < 0 || dst_q < 0)
return 2; // íåïðàâèëüíûé ôîðìàò
long long iq_united64 = 0;
long long iq_final64 = 0;
// Êîíâåðòàöèÿ ê GLOBAL_Q (64-áèò) // Êîíâåðòàöèÿ ê GLOBAL_Q (64-áèò)
if (var->iq_type == t_iq_none) { if (var->iq_type == t_iq_none) {
if (var->ptr_type == pt_float) { if (var->ptr_type == pt_float) {
// float_numb óìíîæàåì íà 2^GLOBAL_Q (2^24=16777216) // float_numb óìíîæàåì íà 2^GLOBAL_Q
// Ðåçóëüòàò ïðèâîäèì ê long long // Ðåçóëüòàò ïðèâîäèì ê 64 áèòà
iq_united64 = (long long)(float_numb * 16777216.0f); iq_united64 = (int64_t)(float_numb * ((uint32_t)1 << GLOBAL_Q));
} else { } else {
iq_united64 = ((long long)iq_numb) << GLOBAL_Q; iq_united64 = ((int64_t)iq_numb) << GLOBAL_Q;
} }
} else { } else {
int shift = GLOBAL_Q - src_q; int shift = GLOBAL_Q - src_q;
if (shift >= 0) if (shift >= 0)
iq_united64 = ((long long)iq_numb) << shift; iq_united64 = ((int64_t)iq_numb) << shift;
else else
iq_united64 = ((long long)iq_numb) >> (-shift); iq_united64 = ((int64_t)iq_numb) >> (-shift);
} }
// Êîíâåðòàöèÿ èç GLOBAL_Q â öåëåâîé IQ (64-áèò) // Êîíâåðòàöèÿ èç GLOBAL_Q â öåëåâîé IQ (64-áèò)
if (var->return_type == t_iq_none) { if (var->return_type == t_iq_none) {
// Âîçâðàùàåì öåëîå, îòáðîñèâ äðîáíóþ ÷àñòü // Âîçâðàùàåì öåëîå, îòáðîñèâ äðîáíóþ ÷àñòü
*ret_var = (long)(iq_united64 >> GLOBAL_Q); *ret_var = (uint32_t)(iq_united64 >> GLOBAL_Q);
} else { } else {
int shift = dst_q - GLOBAL_Q; int shift = dst_q - GLOBAL_Q;
if (shift >= 0) if (shift >= 0)
@ -186,61 +346,71 @@ static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var)
else else
iq_final64 = iq_united64 >> (-shift); iq_final64 = iq_united64 >> (-shift);
// Ïðîâåðÿåì ïåðåïîëíåíèå int32_t *ret_var = (int32_t)iq_final64;
if (iq_final64 > LONG_MAX || iq_final64 < LONG_MIN)
return 3; // ïåðåïîëíåíèå
*ret_var = (long)iq_final64;
} }
return 0; return 0;
} }
static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var) /**
* @brief Ïðî÷èòàòü çíà÷åíèå ïåðåìåííîé îòëàäêè.
* @param var óêàçàòåëü íà ñòðóêòóðó DebugVar.
* @param int_var óêàçàòåëü íà ïåðåìåííóþ òèïà 32 áèòà äëÿ âîçâðàòà öåëî÷èñëåííîãî çíà÷åíèÿ.
* @param float_var óêàçàòåëü íà ïåðåìåííóþ òèïà float äëÿ âîçâðàòà çíà÷åíèÿ ñ ïëàâàþùåé òî÷êîé.
* @return int 0: óñïåõ, 1: îøèáêà óêàçàòåëåé èëè íåïîääåðæèâàåìûé òèï, 3/4: îøèáêà âûðàâíèâàíèÿ.
* @details  çàâèñèìîñòè îò òèïà ïåðåìåííîé ñ÷èòûâàåò å¸ çíà÷åíèå è ñîõðàíÿåò â ñîîòâåòñòâóþùåì óêàçàòåëå.
*/
static int getDebugVar(DebugVar_t *var, int32_t *int_var, float *float_var)
{ {
if (!var || !int_var || !float_var || !var->Ptr) uint8_t *addr = var->Ptr;
return 1; // îøèáêà: null óêàçàòåëü uint32_t addr_val = (uint32_t)addr;
char *addr = var->Ptr; if (!var || !int_var || !float_var || !var->Ptr)
unsigned long addr_val = (unsigned long)addr; return DEBUG_ERR_INTERNAL; // îøèáêà: null óêàçàòåëü
switch (var->ptr_type) switch (var->ptr_type)
{ {
case pt_int8: // 8 áèò case pt_int8: // 8 áèò
if ((addr_val & ALIGN_8BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile int8_t *)addr);
break;
case pt_uint8: case pt_uint8:
// âûðàâíèâàíèå íå íóæíî äëÿ 8 áèò if ((addr_val & ALIGN_8BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
*int_var = *((volatile char *)addr); return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile uint8_t *)addr);
break; break;
case pt_int16: // 16 áèò (int) case pt_int16: // 16 áèò (int)
if ((addr_val & ALIGN_16BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile int16_t *)addr);
break;
case pt_uint16: case pt_uint16:
*int_var = *((volatile int *)addr); if ((addr_val & ALIGN_16BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile uint16_t *)addr);
break; break;
case pt_int32: // 32 áèò (long) case pt_int32: // 32 áèò
if ((addr_val & ALIGN_32BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile int32_t *)addr);
break;
case pt_uint32: case pt_uint32:
if (addr_val & 0x1) // ïðîâåðÿåì âûðàâíèâàíèå ïî 2 ñëîâàì (4 áàéòà) if ((addr_val & ALIGN_32BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return 3; // îøèáêà âûðàâíèâàíèÿ return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile long *)addr); *int_var = *((volatile uint32_t *)addr);
break; break;
// case pt_int64: // 64 áèò (long long)
// case pt_uint64:
// if (addr_val & 0x3) // ïðîâåðêà âûðàâíèâàíèÿ ïî 4 ñëîâàì (8 áàéòàì)
// return 2; // îøèáêà âûðàâíèâàíèÿ
// // Òóò ïðîñòî ÷èòàåì, íî long long ìîæåò íå ïîìåñòèòüñÿ â *int_var
// // Ìîæíî çàìåíèòü ëîãèêó ïîä 64-áèòíîå ÷òåíèå ïðè íåîáõîäèìîñòè
// *int_var = *((volatile long long *)addr);
// break;
case pt_float: // float (4 áàéòà) case pt_float: // float (4 áàéòà)
if (addr_val & 0x1) // ïðîâåðêà âûðàâíèâàíèÿ ïî 2 ñëîâàì if ((addr_val & ALIGN_FLOAT) != 0) // ïðîâåðêà âûðàâíèâàíèÿ
return 4; // îøèáêà âûðàâíèâàíèÿ return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*float_var = *((volatile float *)addr); *float_var = *((volatile float *)addr);
break; break;
default: default:
return 1; // íåïîääåðæèâàåìûé òèï return DEBUG_ERR_INVALID_VAR; // íåïîääåðæèâàåìûé òèï
// äëÿ óêàçàòåëåé è ìàññèâîâ íå ïîääåðæèâàåòñÿ ÷òåíèå // äëÿ óêàçàòåëåé è ìàññèâîâ íå ïîääåðæèâàåòñÿ ÷òåíèå
// case pt_ptr_int8: // case pt_ptr_int8:
// case pt_ptr_int16: // case pt_ptr_int16:
@ -260,210 +430,22 @@ static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var)
} }
///////////// OUTDATE //////////////// /**
* @brief Ïðîâåðÿåò, âõîäèò ëè àäðåñ â îäèí èç äîïóñòèìûõ äèàïàçîíîâ
*
* @param addr_val - Çíà÷åíèå àäðåñà äëÿ ïðîâåðêè
* @param ranges - Óêàçàòåëü íà ìàññèâ äèàïàçîíîâ AddrRange_t
* @param count - Êîëè÷åñòâî äèàïàçîíîâ â ìàññèâå
* @return 0 åñëè àäðåñ íàõîäèòñÿ â îäíîì èç äèàïàçîíîâ, èíà÷å 1
*/
static int is_addr_in_allowed_ranges(uint32_t addr_val, const AddrRange_t *ranges, int count)
{
int i;
// for (i = 0; i < count; i++) {
// // ïðèâåäåíèå ê îäíîìó IQ if (addr_val >= ranges[i].start && addr_val <= ranges[i].end) {
// switch(var->iq_type) return 0;
// { }
// case t_iq_none: }
// if(var->ptr_type == pt_float) return 1;
// { }
// iq_united = _IQ(float_numb);
// }
// else
// {
// iq_united = _IQ(iq_numb);
// }
// break;
// case t_iq1:
// iq_united = _IQ1toIQ(iq_numb);
// break;
// case t_iq2:
// iq_united = _IQ2toIQ(iq_numb);
// break;
// case t_iq3:
// iq_united = _IQ3toIQ(iq_numb);
// break;
// case t_iq4:
// iq_united = _IQ4toIQ(iq_numb);
// break;
// case t_iq5:
// iq_united = _IQ5toIQ(iq_numb);
// break;
// case t_iq6:
// iq_united = _IQ6toIQ(iq_numb);
// break;
// case t_iq7:
// iq_united = _IQ7toIQ(iq_numb);
// break;
// case t_iq8:
// iq_united = _IQ8toIQ(iq_numb);
// break;
// case t_iq9:
// iq_united = _IQ9toIQ(iq_numb);
// break;
// case t_iq10:
// iq_united = _IQ10toIQ(iq_numb);
// break;
// case t_iq11:
// iq_united = _IQ11toIQ(iq_numb);
// break;
// case t_iq12:
// iq_united = _IQ12toIQ(iq_numb);
// break;
// case t_iq13:
// iq_united = _IQ13toIQ(iq_numb);
// break;
// case t_iq14:
// iq_united = _IQ14toIQ(iq_numb);
// break;
// case t_iq15:
// iq_united = _IQ15toIQ(iq_numb);
// break;
// case t_iq16:
// iq_united = _IQ16toIQ(iq_numb);
// break;
// case t_iq17:
// iq_united = _IQ17toIQ(iq_numb);
// break;
// case t_iq18:
// iq_united = _IQ18toIQ(iq_numb);
// break;
// case t_iq19:
// iq_united = _IQ19toIQ(iq_numb);
// break;
// case t_iq20:
// iq_united = _IQ20toIQ(iq_numb);
// break;
// case t_iq21:
// iq_united = _IQ21toIQ(iq_numb);
// break;
// case t_iq22:
// iq_united = _IQ22toIQ(iq_numb);
// break;
// case t_iq23:
// iq_united = _IQ23toIQ(iq_numb);
// break;
// case t_iq24:
// iq_united = _IQ24toIQ(iq_numb);
// break;
// case t_iq25:
// iq_united = _IQ25toIQ(iq_numb);
// break;
// case t_iq26:
// iq_united = _IQ26toIQ(iq_numb);
// break;
// case t_iq27:
// iq_united = _IQ27toIQ(iq_numb);
// break;
// case t_iq28:
// iq_united = _IQ28toIQ(iq_numb);
// break;
// case t_iq29:
// iq_united = _IQ29toIQ(iq_numb);
// break;
// case t_iq30:
// iq_united = _IQ30toIQ(iq_numb);
// break;
// }
//
// // ïðèâåäåíèå îáùåãî IQ ê çàïðàøèâàåìîìó
// switch(var->return_type)
// {
// case t_iq_none:
// iq_final = (long)_IQtoF(iq_united);
// break;
// case t_iq1:
// iq_final = _IQtoIQ1(iq_united);
// break;
// case t_iq2:
// iq_final = _IQtoIQ2(iq_united);
// break;
// case t_iq3:
// iq_final = _IQtoIQ3(iq_united);
// break;
// case t_iq4:
// iq_final = _IQtoIQ4(iq_united);
// break;
// case t_iq5:
// iq_final = _IQtoIQ5(iq_united);
// break;
// case t_iq6:
// iq_final = _IQtoIQ6(iq_united);
// break;
// case t_iq7:
// iq_final = _IQtoIQ7(iq_united);
// break;
// case t_iq8:
// iq_final = _IQtoIQ8(iq_united);
// break;
// case t_iq9:
// iq_final = _IQtoIQ9(iq_united);
// break;
// case t_iq10:
// iq_final = _IQtoIQ10(iq_united);
// break;
// case t_iq11:
// iq_final = _IQtoIQ11(iq_united);
// break;
// case t_iq12:
// iq_final = _IQtoIQ12(iq_united);
// break;
// case t_iq13:
// iq_final = _IQtoIQ13(iq_united);
// break;
// case t_iq14:
// iq_final = _IQtoIQ14(iq_united);
// break;
// case t_iq15:
// iq_final = _IQtoIQ15(iq_united);
// break;
// case t_iq16:
// iq_final = _IQtoIQ16(iq_united);
// break;
// case t_iq17:
// iq_final = _IQtoIQ17(iq_united);
// break;
// case t_iq18:
// iq_final = _IQtoIQ18(iq_united);
// break;
// case t_iq19:
// iq_final = _IQtoIQ19(iq_united);
// break;
// case t_iq20:
// iq_final = _IQtoIQ20(iq_united);
// break;
// case t_iq21:
// iq_final = _IQtoIQ21(iq_united);
// break;
// case t_iq22:
// iq_final = _IQtoIQ22(iq_united);
// break;
// case t_iq23:
// iq_final = _IQtoIQ23(iq_united);
// break;
// case t_iq24:
// iq_final = _IQtoIQ24(iq_united);
// break;
// case t_iq25:
// iq_final = _IQtoIQ25(iq_united);
// break;
// case t_iq26:
// iq_final = _IQtoIQ26(iq_united);
// break;
// case t_iq27:
// iq_final = _IQtoIQ27(iq_united);
// break;
// case t_iq28:
// iq_final = _IQtoIQ28(iq_united);
// break;
// case t_iq29:
// iq_final = _IQtoIQ29(iq_united);
// break;
// case t_iq30:
// iq_final = _IQtoIQ30(iq_united);
// break;
// }
// *ret_var = iq_final;

View File

@ -1,8 +1,65 @@
#ifndef DEBUG_TOOLS #ifndef DEBUG_TOOLS
#define DEBUG_TOOLS #define DEBUG_TOOLS
#include "IQmathLib.h" #include <stdint.h>
#include "DSP281x_Device.h" #include <limits.h>
#define ALLOWED_ADDRESS_RANGES { \
{0x000000, 0x0007FF}, \
{0x008120, 0x009FFC}, \
{0x3F8000, 0x3F9FFF}, \
{0x3FF000, 0x3FFFFF}, \
{0x080002, 0x09FFFF}, \
{0x0F0000, 0x0FFEFF}, \
{0x100002, 0x103FFF}, \
{0x102000, 0x103FFF} \
}
#if UINT8_MAX // Åñëè åñòü òèï 8 áèò - çí÷à÷èò àäðåñàöèÿ ïî 8 áèò
#define ALIGN_8BIT 0x0 ///< Âûðàâíèâàíèå áåç îãðàíè÷åíèé (ëþáîé àäðåñ)
#define ALIGN_16BIT 0x1 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 2 (addr & 0x1 == 0)
#define ALIGN_32BIT 0x3 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 4 (addr & 0x3 == 0)
#define ALIGN_64BIT 0x7 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 8 (addr & 0x7 == 0)
#define ALIGN_FLOAT ALIGN_32BIT
#else // Åñëè íåò òèïà 8 áèò - çíà÷èò àäðåñàöèÿ ïî 16 áèò
#define ALIGN_8BIT 0x0 ///< Âûðàâíèâàíèå áåç îãðàíè÷åíèé (ëþáîé àäðåñ)
#define ALIGN_16BIT 0x0 ///< Âûðàâíèâàíèå áåç îãðàíè÷åíèé (ëþáîé àäðåñ)
#define ALIGN_32BIT 0x1 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 4 (addr & 0x1 == 0)
#define ALIGN_64BIT 0x3 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 8 (addr & 0x3 == 0)
#define ALIGN_FLOAT ALIGN_32BIT
#endif //STM32/TMS32
#if !UINT8_MAX
typedef unsigned char uint8_t;
typedef signed char int8_t;
#endif
#if !defined(NULL)
#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
#define DEBUG_ERR_RS (1<<6) | DEBUG_ERR
/**
* @brief Òèï äàííûõ, íà êîòîðûé óêàçûâàåò óêàçàòåëü ïåðåìåííîé îòëàäêè.
*/
typedef enum typedef enum
{ {
pt_unknown, // unknown pt_unknown, // unknown
@ -14,7 +71,7 @@ typedef enum
pt_uint16, // unsigned int pt_uint16, // unsigned int
pt_uint32, // unsigned long pt_uint32, // unsigned long
pt_uint64, // unsigned long pt_uint64, // unsigned long
pt_float, // float pt_float, // floatf
pt_struct, // struct pt_struct, // struct
pt_union, // struct pt_union, // struct
// pt_ptr_int8, // signed char* // pt_ptr_int8, // signed char*
@ -31,6 +88,9 @@ typedef enum
// pt_arr_uint32, // unsigned long[] // pt_arr_uint32, // unsigned long[]
}DebugVarPtrType_t; }DebugVarPtrType_t;
/**
* @brief Òèïû IQ-ïðåäñòàâëåíèÿ ïåðåìåííîé îòëàäêè.
*/
typedef enum typedef enum
{ {
t_iq_none, t_iq_none,
@ -67,45 +127,83 @@ typedef enum
t_iq30 t_iq30
}DebugVarIQType_t; }DebugVarIQType_t;
typedef char DebugVarName_t[11]; typedef char DebugVarName_t[11]; ///< Èìÿ ïåðåìåííîé îòëàäêè (äî 10 ñèìâîëîâ + \0)
/**
* @brief Îïèñàíèå ïåðåìåííîé îòëàäêè.
*/
typedef struct typedef struct
{ {
char* Ptr; uint8_t* Ptr; ///< Óêàçàòåëü íà çíà÷åíèå ïåðåìåííîé
DebugVarPtrType_t ptr_type; DebugVarPtrType_t ptr_type; ///< Òèï çíà÷åíèÿ
DebugVarIQType_t iq_type; DebugVarIQType_t iq_type; ///< Òèï IQ ïåðåìåííîé (åñëè åñòü)
DebugVarIQType_t return_type; DebugVarIQType_t return_type;///< Òèï IQ âîçâðàùàåìîãî çíà÷åíèÿ
DebugVarName_t name; DebugVarName_t name; ///< Èìÿ ïåðåìåííîé
}DebugVar_t; } DebugVar_t;
typedef long DebugValue_t;
/**
* @brief Ñòðóêòóðà äàòû è âðåìåíè.
*/
typedef struct { typedef struct {
int year; uint16_t year; ///< Ãîä (íàïðèìåð, 2025)
char month; uint8_t month; ///< Ìåñÿö (1-12)
char day; uint8_t day; ///< Äåíü (1-31)
char hour; uint8_t hour; ///< ×àñû (0-23)
char minute; uint8_t minute; ///< Ìèíóòû (0-59)
} DateTime_t; } DateTime_t;
/**
* @brief Ñòðóêòóðà, îïèñûâàþùàÿ äèàïàçîí àäðåñîâ ïàìÿòè.
*/
typedef struct {
uint32_t start; ///< Íà÷àëüíûé àäðåñ äèàïàçîíà
uint32_t end; ///< Êîíå÷íûé àäðåñ äèàïàçîíà (âêëþ÷èòåëüíî)
} AddrRange_t;
/**
* @brief Ñòðóêòóðà íèæíåãî óðîâíÿ îòëàäêè.
*/
typedef struct typedef struct
{ {
DateTime_t build_date; DateTime_t build_date; ///< Äàòà ñáîðêè
unsigned int isVerified; unsigned int isVerified; ///< Ôëàã èíèöèàëèçàöèè íèçêîóðîâíåíîé îòëàäêè (0 — íåò, 1 — óñïåøíî)
DebugVar_t dbg_var; DebugVar_t dbg_var; ///< Ïåðåìåííàÿ äëÿ îòëàäêè
}DebugLowLevel_t; }DebugLowLevel_t;
extern DebugLowLevel_t debug_ll; extern DebugLowLevel_t debug_ll; ///< Ãëîáàëüíûé ýêçåìïëÿð îòëàäêè íèæíåãî óðîâíÿ
/** @brief Ìàêðîñ èíèöèàëèçàöèè äàòû */
#define DATE_INIT {BUILD_YEAR, BUILD_MONTH, BUILD_DATA, BUILD_HOURS, BUILD_MINUTES} #define DATE_INIT {BUILD_YEAR, BUILD_MONTH, BUILD_DATA, BUILD_HOURS, BUILD_MINUTES}
/** @brief Ìàêðîñ èíèöèàëèçàöèè ïåðåìåííîé îòëàäêè */
#define DEBUG_VAR_INIT {0, pt_uint16, t_iq_none, t_iq_none, "\0"} #define DEBUG_VAR_INIT {0, pt_uint16, t_iq_none, t_iq_none, "\0"}
/** @brief Ìàêðîñ èíèöèàëèçàöèè íèæíåãî óðîâíÿ îòëàäêè */
#define DEBUG_LOWLEVEL_INIT {DATE_INIT, 0, DEBUG_VAR_INIT} #define DEBUG_LOWLEVEL_INIT {DATE_INIT, 0, DEBUG_VAR_INIT}
extern int DebugVar_Qnt; extern int DebugVar_Qnt; ///< Êîëè÷åñòâî ïåðåìåííûõ îòëàäêè
extern DebugVar_t dbg_vars[]; extern DebugVar_t dbg_vars[]; ///< Ìàññèâ ïåðåìåííûõ îòëàäêè
/* Ïðèìåð èñïîëüçîâàíèÿ îòëàäêè */
void Debug_Test_Example(void); void Debug_Test_Example(void);
int Debug_ReadVar(int var_ind, long *return_long); /* ×èòàåò çíà÷åíèå ïåðåìåííîé ïî èíäåêñó */
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr); int Debug_ReadVar(int var_ind, int32_t *return_long);
int Debug_LowLevel_ReadVar(long *return_long); /* ×èòàåò èìÿ ïåðåìåííîé ïî èíäåêñó */
int Debug_LowLevel_Initialize(const char* external_date); 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);
/* Èíèöèàëèçèðóåò îòëàäêó íèæíåãî óðîâíÿ */
int Debug_LowLevel_Initialize(DateTime_t *external_date);
/* ×èòàåò âîçâðàùàåìûé òèï (IQ) íèçêîóðîâíåíî çàäàííîé ïåðåìåííîé */
int Debug_LowLevel_ReadVarReturnType(int *vartype);
/* ×èòàåò òèï íèçêîóðîâíåíî çàäàííîé ïåðåìåííîé.*/
int Debug_LowLevel_ReadVarType(int *vartype);
#endif //DEBUG_TOOLS #endif //DEBUG_TOOLS

380
parse_xml/Src/parse_xml.py Normal file
View File

@ -0,0 +1,380 @@
# pyinstaller --onefile --distpath ./parse_xml --workpath ./build --specpath ./build parse_xml/Src/parse_xml.py
# python -m nuitka --standalone --onefile --output-dir=./build parse_xml/Src/parse_xml.py
import xml.etree.ElementTree as ET
import xml.dom.minidom
import sys
import os
if len(sys.argv) < 3:
print("Usage: python simplify_dwarf.py <input.xml> <info.txt> [output.xml]")
sys.exit(1)
input_path = sys.argv[1]
info_path = sys.argv[2]
base_type_sizes = {
"char": 2,
"short": 2,
"int": 2,
"long": 4,
"long long": 8,
"float": 4,
"double": 8,
}
if len(sys.argv) >= 4:
output_path = sys.argv[3]
else:
input_dir = os.path.dirname(os.path.abspath(input_path))
output_path = os.path.join(input_dir, "simplified.xml")
tree = ET.parse(input_path)
root = tree.getroot()
def extract_timestamp(info_path):
with open(info_path, "r", encoding="utf-8") as f:
for line in f:
if "Time Stamp:" in line:
parts = line.split("Time Stamp:")
if len(parts) > 1:
timestamp = parts[1].strip()
return timestamp
die_by_id = {die.attrib.get("id"): die for die in root.iter("die") if "id" in die.attrib}
def get_attr(die, attr_type):
for attr in die.findall("attribute"):
type_elem = attr.find("type")
if type_elem is not None and type_elem.text == attr_type:
return attr.find("value")
return None
def get_die_size(die):
"""Вернуть размер DIE в байтах из атрибута DW_AT_byte_size или по ключевым словам имени типа."""
# Сначала пытаемся получить размер из DW_AT_byte_size
for attr in die.findall("attribute"):
type_elem = attr.find("type")
if type_elem is not None and type_elem.text == "DW_AT_byte_size":
const_elem = attr.find("value/const")
if const_elem is not None:
return int(const_elem.text, 0)
# Если не нашли, пробуем определить размер по ключевым словам в имени типа
name_elem = die.find("attribute[@name='DW_AT_name']/value/const")
if name_elem is not None:
type_name = name_elem.text.lower()
for key, size in base_type_sizes.items():
if key in type_name:
return size
return None
def resolve_type_die(type_id):
"""Получить DIE типа, разрешая typedef, const и volatile."""
visited = set()
while type_id and type_id not in visited:
visited.add(type_id)
die = die_by_id.get(type_id)
if die is None:
return None
tag = die.findtext("tag")
if tag in ("DW_TAG_volatile_type", "DW_TAG_const_type", "DW_TAG_typedef", "DW_TAG_TI_far_type"):
ref = get_attr(die, "DW_AT_type")
if ref is not None and ref.find("ref") is not None:
type_id = ref.find("ref").attrib.get("idref")
else:
return None
else:
return die
return None
# Словарь для простых базовых типов по тегам (пример)
base_types_map = {
"DW_TAG_base_type": lambda die: die.find("attribute[@type='DW_AT_name']/value/string").text if die.find("attribute[@type='DW_AT_name']/value/string") is not None else "unknown",
"DW_TAG_structure_type": lambda die: "struct",
"DW_TAG_union_type": lambda die: "union",
"DW_TAG_pointer_type": lambda die: "pointer",
"DW_TAG_array_type": lambda die: "array",
}
def get_type_name(type_id):
die = resolve_type_die(type_id)
if die is None:
return "unknown"
tag = die.findtext("tag")
if tag == "DW_TAG_pointer_type":
ref = get_attr(die, "DW_AT_type")
if ref is not None and ref.find("ref") is not None:
pointee_id = ref.find("ref").attrib.get("idref")
name = get_type_name(pointee_id)
return name + "*" if name != "unknown" else name
else:
return "void*"
elif tag == "DW_TAG_base_type":
name_attr = get_attr(die, "DW_AT_name")
if name_attr is not None:
return name_attr.findtext("string")
else:
return "base_type_unknown"
elif tag == "DW_TAG_structure_type":
name_attr = get_attr(die, "DW_AT_name")
name = name_attr.findtext("string") if name_attr is not None else "anonymous_struct"
return f"struct {name}"
elif tag == "DW_TAG_union_type":
name_attr = get_attr(die, "DW_AT_name")
name = name_attr.findtext("string") if name_attr is not None else "anonymous_union"
return f"union {name}"
elif tag == "DW_TAG_array_type":
ref = get_attr(die, "DW_AT_type")
if ref is not None and ref.find("ref") is not None:
element_type_id = ref.find("ref").attrib.get("idref")
element_type_name = get_type_name(element_type_id)
return f"{element_type_name}[]"
else:
return "array[]"
# Добавляем поддержку enum
elif tag == "DW_TAG_enumeration_type":
name_attr = get_attr(die, "DW_AT_name")
name = name_attr.findtext("string") if name_attr is not None else "anonymous_enum"
return f"enum {name}"
else:
return "unknown"
def parse_offset(offset_text):
if offset_text and offset_text.startswith("DW_OP_plus_uconst "):
return int(offset_text.split()[-1], 0)
return 0
def get_array_dimensions(array_die):
"""
Собрать размеры всех измерений массива.
Возвращает список размеров [dim0, dim1, ...] во внешне-внутреннем порядке.
"""
dims = []
# Ищем DW_TAG_subrange_type — для каждого измерения
for child in array_die.findall("die"):
if child.findtext("tag") != "DW_TAG_subrange_type":
continue
dim_size = None
# Попытка получить upper_bound
ub_attr = get_attr(child, "DW_AT_upper_bound")
if ub_attr is not None:
val = ub_attr.find("value/const")
if val is not None:
try:
# В DWARF верхняя граница включительно, значит размер = upper_bound + 1
dim_size = int(val.text, 0) + 1
except Exception:
pass
# Если не получилось — попытаться из count
if dim_size is None:
ct_attr = get_attr(child, "DW_AT_count")
if ct_attr is not None:
val = ct_attr.find("value/const")
if val is not None:
try:
dim_size = int(val.text, 0)
except Exception:
pass
# Если ничего не найдено — ставим 0
if dim_size is None:
dim_size = 0
dims.append(dim_size)
# Если subrange не нашли (например, в случае typedef), попробуем через размер типа
if not dims:
arr_size = get_die_size(array_die)
elem_size = None
element_type_ref = get_attr(array_die, "DW_AT_type")
if element_type_ref is not None and element_type_ref.find("ref") is not None:
element_type_id = element_type_ref.find("ref").attrib.get("idref")
elem_die = resolve_type_die(element_type_id)
if elem_die is not None:
elem_size = get_die_size(elem_die)
if arr_size is not None and elem_size:
dims.append(arr_size // elem_size)
else:
dims.append(0)
# Рекурсия — если элементный тип массива тоже массив, добавляем размеры вложенного
element_type_ref = get_attr(array_die, "DW_AT_type")
if element_type_ref is not None and element_type_ref.find("ref") is not None:
element_type_id = element_type_ref.find("ref").attrib.get("idref")
element_type_die = resolve_type_die(element_type_id)
if element_type_die is not None and element_type_die.findtext("tag") == "DW_TAG_array_type":
dims.extend(get_array_dimensions(element_type_die))
return dims
def get_base_type_die(array_die):
"""Спускаемся по цепочке DW_AT_type, пока не дойдем до не-массива (базового типа)."""
current_die = array_die
while True:
ref = get_attr(current_die, "DW_AT_type")
if ref is None or ref.find("ref") is None:
break
next_die = resolve_type_die(ref.find("ref").attrib.get("idref"))
if next_die is None:
break
if next_die.findtext("tag") == "DW_TAG_array_type":
current_die = next_die
else:
return next_die
return current_die
def handle_array_type(member_elem, resolved_type, offset=0):
dims = get_array_dimensions(resolved_type)
# Определяем базовый тип (не массив)
base_die = get_base_type_die(resolved_type)
base_name = "unknown"
base_size = None
if base_die is not None:
base_id = base_die.attrib.get("id")
if base_id:
base_name = get_type_name(base_id)
base_size = get_die_size(base_die)
else:
base_name = get_type_name(base_die.attrib.get("id", ""))
member_elem.set("type", base_name + "[]" * len(dims))
# Вычисляем общий размер массива — произведение размеров * размер базового элемента
if base_size is None:
base_size = 0
total_elements = 1
for d in dims:
if d == 0:
total_elements = 0
break
total_elements *= d
total_size = total_elements * base_size if base_size is not None else 0
if total_size:
member_elem.set("size", str(total_size))
else:
# fallback: если не удалось, можно попробовать get_die_size
arr_size = get_die_size(resolved_type)
if arr_size:
member_elem.set("size", str(arr_size))
# Записываем размеры измерений size1, size2 ...
for i, dim in enumerate(dims, 1):
member_elem.set(f"size{i}", str(dim))
member_elem.set("kind", "array")
# Если элемент базового типа — структура, разворачиваем её поля
if base_die is not None and base_die.findtext("tag") == "DW_TAG_structure_type":
add_members_recursive(member_elem, base_die, offset)
def add_members_recursive(parent_elem, struct_die, base_offset=0):
is_union = struct_die.findtext("tag") == "DW_TAG_union_type"
size = get_die_size(struct_die)
if size is not None:
parent_elem.set("size", hex(size))
for member in struct_die.findall("die"):
if member.findtext("tag") != "DW_TAG_member":
continue
name_attr = get_attr(member, "DW_AT_name")
offset_attr = get_attr(member, "DW_AT_data_member_location")
type_attr = get_attr(member, "DW_AT_type")
if name_attr is None or offset_attr is None or type_attr is None:
continue
name = name_attr.findtext("string")
offset = parse_offset(offset_attr.findtext("block")) + base_offset
type_id = type_attr.find("ref").attrib.get("idref")
resolved_type = resolve_type_die(type_id)
type_name = get_type_name(type_id)
if type_name == "unknown":
continue
member_elem = ET.SubElement(parent_elem, "member", name=name, offset=hex(offset), type=type_name)
if is_union:
member_elem.set("kind", "union")
if resolved_type is not None:
tag = resolved_type.findtext("tag")
if tag == "DW_TAG_array_type":
handle_array_type(member_elem, resolved_type, offset)
elif tag in ("DW_TAG_structure_type", "DW_TAG_union_type"):
member_elem.set("type", type_name)
add_members_recursive(member_elem, resolved_type, offset)
output_root = ET.Element("variables")
for die in root.iter("die"):
if die.findtext("tag") != "DW_TAG_variable":
continue
name_attr = get_attr(die, "DW_AT_name")
addr_attr = get_attr(die, "DW_AT_location")
type_attr = get_attr(die, "DW_AT_type")
if name_attr is None or addr_attr is None or type_attr is None:
continue
name = name_attr.findtext("string")
if "$" in name:
continue
addr_text = addr_attr.findtext("block")
if not addr_text or not addr_text.startswith("DW_OP_addr "):
continue
addr = int(addr_text.split()[-1], 0)
type_id = type_attr.find("ref").attrib.get("idref")
resolved_type = resolve_type_die(type_id)
type_name = get_type_name(type_id)
if 0x800 <= addr < 0x8000 or type_name == "unknown":
continue
var_elem = ET.SubElement(output_root, "variable", name=name, address=hex(addr), type=type_name)
if resolved_type is not None:
tag = resolved_type.findtext("tag")
if tag == "DW_TAG_array_type":
handle_array_type(var_elem, resolved_type)
elif tag in ("DW_TAG_structure_type", "DW_TAG_union_type"):
add_members_recursive(var_elem, resolved_type)
timestamp = extract_timestamp(info_path)
timestamp_elem = ET.Element("timestamp")
timestamp_elem.text = timestamp
output_root.insert(0, timestamp_elem)
rough_string = ET.tostring(output_root, encoding="utf-8")
pretty_xml = xml.dom.minidom.parseString(rough_string).toprettyxml(indent=" ")
with open(output_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
os.remove(input_path)
os.remove(info_path)
print(f"Simplified and formatted XML saved to: {output_path}")

BIN
parse_xml/parse_xml.exe Normal file

Binary file not shown.