18 Commits

Author SHA1 Message Date
Razvalyaev
043359fe66 + фикс кривых проверок на наличие uint8_t
+ фикс перевод float в iq
+ фикс поиска вручнуб добавленных переменных в debug_vars.c
2025-07-17 10:37:58 +03:00
Razvalyaev
c55f38ef1c + пример debug_vars.c
+ exe
+ опечатка в readme
+ коррекции по абзацам в комментах
2025-07-17 09:26:57 +03:00
Razvalyaev
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
Razvalyaev
4de53090a1 добавлены комменты к debug_tools.c/.h
начата работа над поддержкой stm32 и кейл проектов
2025-07-15 19:05:52 +03:00
Razvalyaev
742c4e9e1b readme для .c файлов 2025-07-15 15:56:47 +03:00
Razvalyaev
369cfa808c чистка для релиза
переструктурировано чуть

и всякие мелкие фиксы
2025-07-15 15:37:29 +03:00
Razvalyaev
c32dc161f8 переструктурирвоаны исходники проекта 2025-07-15 13:31:18 +03:00
Razvalyaev
abfc507e4e регулярнка не работает в clean_remp 2025-07-15 13:13:08 +03:00
Razvalyaev
cb496bca0f добавлен readme
другая библиотека для xml (более быстрая)
сделан выбор элементов в выпадающем списке типов
исправлено вроде кривое выставлениеп return_type
изменено окно выбора переменых
    - кнопки добавит, удалить заменены на применить
    - исправлены кривые подсказки в .exe версии
2025-07-15 13:03:11 +03:00
Razvalyaev
7b720cbdf4 криво работает return_type, надо разобратся
чет поделано в коде для тмс
2025-07-14 17:54:49 +03:00
Razvalyaev
6428e523df сделано ленивое раскрытие подпеременных в структурах
(баово заглушки и если они раскрываются,то подставляются реальнгые)
2025-07-14 14:57:32 +03:00
Razvalyaev
c738acd871 фикс мелочей в таблицах выбора
+exe
+3.13 python installer заменен на 3.9 (3.13 не имеет PySide2)
2025-07-14 07:35:28 +03:00
Razvalyaev
05bde87c38 +exe 2025-07-13 22:53:58 +03:00
Razvalyaev
02f3124224 ну вроде чуть поменьше тормозит, но все равно неприятно. надо еще подумать 2025-07-13 22:47:33 +03:00
Razvalyaev
21082a38e0 ну вроде работает, но опять тормозит 2025-07-13 22:17:45 +03:00
Razvalyaev
42ac3eb65d наконец-то вроде сделан поиск.
есть кривости в подскасках
начата работа надо двумя таблицами (всех и выбранных переменных)
2025-07-13 18:53:01 +03:00
Razvalyaev
69c0bf1574 почему-то внуки, правнуки и так далее в поиске на находятся... 2025-07-12 18:13:51 +03:00
Razvalyaev
4f949e9854 таблица выбора элементов вынесена в отдельный класс 2025-07-12 08:54:53 +03:00
24 changed files with 2901 additions and 26974 deletions

Binary file not shown.

172
README.md Normal file
View File

@@ -0,0 +1,172 @@
# 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.exe`, не требующий установки и дополнительных зависимостей.
> Требуется Windows 7 или новее.
---
## Как использовать приложение
1. Запустите **DebugVarEdit.exe.**
2. Укажите пути и контроллер:
- **Путь к XML** — новый или существующий файл настроек переменных;
- **Путь к проекту** — путь к корню проека (папка, которая является корнем для проекта в Code Composer);
- **Путь к makefile** — путь к `makefile` относительно корня проекта;
- **Путь для debug_vars.c** — папка, куда будет сгенерирован файл `debug_vars.c` с переменными.
- **МК** — TMS или STM. Они отличаются размером int, и также принятыми кодировками файлов (utf-8/cp1251)
3. Нажмите **Сканировать переменные**:
- Программа проанализирует исходники, указанные в makefile, и найдёт все переменные.
- Результат сохранится в двух XML-файлах:
- `structs.xml` — информация обо всех структурах и typedef;
- `<ваш_файл>.xml` — список всех найденных переменных.
4. Нажмите **Добавить переменные**:
- В **левой таблице** отображаются все найденные переменные.
Для добавления переменной в проект дважды кликните по ней или нажмите кнопку `>`, чтобы переместить в правую таблицу.
- В **правой таблице** находятся переменные, выбранные для использования.
Чтобы убрать переменную из проекта, переместите её обратно в левую таблицу двойным кликом или кнопкой `<`.
- После выбора переменных нажмите **Применить**, чтобы обновить основной список переменных и включить их в проект.
5. Настройте параметры выбранных переменных:
- **En** — включение или отключение переменной для генерации;
- **Base Type** — базовый тип переменной (например, int8, uint16 и т.д.);
- **IQ Type** — формат IQ, если применимо;
- **Return Type** — формат возвращаемого значения (IQ-тип или целочисленный);
- **Shortname** — короткое имя переменной для для терминалки.
Все параметры выбираются из выпадающих списков, которые можно настроить, чтобы отображались только нужные опции.
6. Нажмите **Сгенерировать файл** для создания файла `debug_vars.c` с выбранными переменными.
---
## Возможности
- Загрузка и сохранение настроек переменных в XML-файлах.
- Автоматическое определение исходных файлов с переменными для удобства работы.
- Редактирование переменных: включение, короткого имени и типов через удобные списки.
- Подсветка ошибок при вводе (неправильные имена, слишком длинные короткие имена).
- Быстрая фильтрация переменных по столбцам.
- Автоматическая генерация файла debug_vars.c с выбранными переменными.
- Возможность сразу открыть сгенерированный файл в редакторе.
- Умное автодополнение имён переменных и полей структур.
---
## Пример XML-файла
```xml
<project proj_path="C:/myproj" makefile_path="Debug/Makefile" structs_path="Src/DebugTools/structs.xml">
<variables>
<var name="g_myvar">
<enable>true</enable>
<show_var>true</show_var>
<shortname>myv</shortname>
<pt_type>pt_float</pt_type>
<iq_type>t_iq24</iq_type>
<return_type>t_iq24</return_type>
<type>float</type>
<file>Src/main/main.c</file>
<extern>true</extern>
<static>false</static>
</var>
</variables>
</project>
```
---
---
# Для разработчиков
### Структура проекта:
```bash
Src
├── build/
│ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller)
├── DebugVarEdit_GUI.py # Главное окно
├── var_table.py # Таблица выбранных переменных
├── var_selector_window.py # Окно выбора переменных
├── var_selector_table.py # Таблица переменных в окне выбора переменных
├── scan_progress_gui.py # Отображение процесса сканирования переменных
├── scan_vars.py # Сканирование переменных среди .c/.h файлов
├── generate_debug_vars.py # Генерация debug_vars.c
├── myXML.py # Утилиты для XML
├── makefile_parser.py # Парсинг makefile на .c/.h файлы
├── var_setup.py # Подготовка переменных для окна выбора переменных
├── libclang.dll # Бибилиотека clang
├── icon.ico # Иконка
```
### Зависимости
Для запуска приложения:
- **Python 3.7+**
- **clang** — используется для парсинга C-кода (требуется `libclang.dll`, лежит в папке Src)
- **PySide2** — GUI-фреймворк
- **lxml** — работа с XML
> Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7
Для сборки `.exe`:
- **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей
- **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен для запуска `.exe`)
### Сборка:
Если вы хотите собрать `DebugVarEdit.exe` самостоятельно из исходников, используйте скрипт **build/build_and_clean.py**. Он автоматически собирает проект с помощью Nuitka или PyInstaller:
- Собирает проект в `DebugVarEdit.exe` в корневой папке.
- Включает:
- все необходимые `.dll` (например, `libclang.dll`),
- иконку (`icon.ico`),
- плагины PySide2.
- Очищает временные папки после сборки:
- `build_temp`
- `__pycache__`
- `DebugVarEdit_GUI.*`
> Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`.
### Установка зависимостей
```bash
pip install clang PySide2 lxml nuitka pyinstaller
```

View File

@@ -1,19 +1,19 @@
# build command # build command
# pyinstaller --onefile --name DebugVarEdit --add-binary "build/libclang.dll;build" --distpath ./ --workpath ./build_temp --specpath ./build_temp setupVars_GUI.py # pyinstaller --onefile --name DebugVarEdit --add-binary "build/libclang.dll;build" --distpath ./ --workpath ./build_temp --specpath ./build_temp var_setup_GUI.py
import sys import sys
import os import os
import subprocess import subprocess
import xml.etree.ElementTree as ET import lxml.etree as ET
from generateVars import type_map from generate_debug_vars import type_map, choose_type_map
from enum import IntEnum from enum import IntEnum
import threading import threading
from generateVars import run_generate from generate_debug_vars import run_generate
import setupVars import var_setup
from VariableSelector import VariableSelectorDialog from var_selector_window import VariableSelectorDialog
from VariableTable import VariableTableWidget, rows from var_table import VariableTableWidget, rows
from scanVarGUI import ProcessOutputWindow from scan_progress_gui import ProcessOutputWindow
import scanVars import scan_vars
import myXML import myXML
import time import time
@@ -21,8 +21,9 @@ 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
@@ -31,12 +32,18 @@ var_edit_title = "Редактор переменных для отладки"
xml_path_title = "Путь к XML:" xml_path_title = "Путь к XML:"
proj_path_title = "Путь к проекту:" proj_path_title = "Путь к проекту:"
makefile_path_title = "Пусть к makefile (относительно проекта)" makefile_path_title = "Пусть к makefile (относительно проекта)"
output_path_title = "Папка для debug_vars.c:" output_path_title = "Путь для для debug_vars.c:"
scan_title = "Сканировать переменные" scan_title = "Сканировать переменные"
build_title = "Сгенерировать файл" build_title = "Сгенерировать файл"
add_vars_title = "Добавить переменные" add_vars_title = "Добавить переменные"
open_output_title = "Открыть файл" open_output_title = "Открыть файл"
def set_sub_elem_text(parent, tag, text):
el = parent.find(tag)
if el is None:
el = ET.SubElement(parent, tag)
el.text = str(text)
# 3. UI: таблица с переменными # 3. UI: таблица с переменными
class VarEditor(QWidget): class VarEditor(QWidget):
def __init__(self): def __init__(self):
@@ -52,12 +59,13 @@ 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):
self.setWindowTitle(var_edit_title) self.setWindowTitle(var_edit_title)
base_path = scanVars.get_base_path() base_path = scan_vars.get_base_path()
icon_path = os.path.join(base_path, "icon.ico") icon_path = os.path.join(base_path, "icon.ico")
if os.path.exists(icon_path): if os.path.exists(icon_path):
self.setWindowIcon(QIcon(icon_path)) self.setWindowIcon(QIcon(icon_path))
@@ -116,6 +124,32 @@ 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)
menubar.addMenu(self.target_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)
@@ -131,6 +165,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)
@@ -144,7 +179,20 @@ class VarEditor(QWidget):
self.setLayout(layout) self.setLayout(layout)
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
@@ -202,7 +250,7 @@ class VarEditor(QWidget):
# Создаём окно с кнопкой "Готово" # Создаём окно с кнопкой "Готово"
self.proc_win = ProcessOutputWindow(self.proj_path, self.makefile_path, self.xml_path, self.proc_win = ProcessOutputWindow(self.proj_path, self.makefile_path, self.xml_path,
on_done_callback=self.__after_scanvars_finished) self.__after_scan_vars_finished, self)
self.proc_win.start_scan() self.proc_win.start_scan()
@@ -222,8 +270,8 @@ class VarEditor(QWidget):
var_data = { var_data = {
'name': name_edit.text(), 'name': name_edit.text(),
'type': 'pt_' + pt_type_combo.currentText(), 'type': 'pt_' + pt_type_combo.currentText(),
'iq_type': iq_combo.currentText(), 'iq_type': 't_' + iq_combo.currentText(),
'return_type': ret_combo.currentText() if ret_combo.currentText() else 'int', 'return_type': 't_' + ret_combo.currentText() if ret_combo.currentText() else 't_iq_none',
'short_name': short_name_edit.text(), 'short_name': short_name_edit.text(),
} }
vars_out.append(var_data) vars_out.append(var_data)
@@ -235,15 +283,15 @@ 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:
QMessageBox.critical(self, "Ошибка при генерации", str(e)) QMessageBox.critical(self, "Ошибка при генерации", str(e))
def update(self): def update(self, force=0):
if self._updating: if self._updating and (force==0):
return # Уже в процессе обновления — выходим, чтобы избежать рекурсии return # Уже в процессе обновления — выходим, чтобы избежать рекурсии
self._updating = True self._updating = True
@@ -296,11 +344,11 @@ class VarEditor(QWidget):
structs_path_full = myXML.make_absolute_path(structs_path, self.proj_path) structs_path_full = myXML.make_absolute_path(structs_path, self.proj_path)
if structs_path_full and os.path.isfile(structs_path_full): if structs_path_full and os.path.isfile(structs_path_full):
self.structs_path = structs_path_full self.structs_path = structs_path_full
self.structs, self.typedef_map = setupVars.parse_structs(structs_path_full) self.structs, self.typedef_map = var_setup.parse_structs(structs_path_full)
else: else:
self.structs_path = None self.structs_path = None
self.vars_list = setupVars.parse_vars(self.xml_path, self.typedef_map) self.vars_list = var_setup.parse_vars(self.xml_path, self.typedef_map)
self.table.populate(self.vars_list, self.structs, self.write_to_xml) self.table.populate(self.vars_list, self.structs, self.write_to_xml)
except Exception as e: except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}") QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
@@ -352,10 +400,20 @@ class VarEditor(QWidget):
super().keyPressEvent(event) super().keyPressEvent(event)
def __browse_makefile(self): def __browse_makefile(self):
file_path, _ = QFileDialog.getOpenFileName( if self.target == 'stm':
self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)" file_filter = "Makefile или Keil-проект (*.uvprojx *.uvproj makefile);;Все файлы (*)"
) dialog_title = "Выберите Makefile или Keil-проект"
if file_path and self.proj_path: else: # 'TMS' или по умолчанию
file_filter = "Makefile (makefile);;Все файлы (*)"
dialog_title = "Выберите Makefile"
file_path, _ = QFileDialog.getOpenFileName(
self,
dialog_title,
filter=file_filter
)
if file_path:
if self.proj_path:
path = myXML.make_relative_path(file_path, self.proj_path) path = myXML.make_relative_path(file_path, self.proj_path)
else: else:
path = file_path path = file_path
@@ -398,50 +456,66 @@ class VarEditor(QWidget):
self.update() self.update()
def __after_scanvars_finished(self): def __after_scan_vars_finished(self):
self.update_all_paths()
if not os.path.isfile(self.xml_path): if not os.path.isfile(self.xml_path):
self.makefile_path = None
self.structs_path = None
self.proj_path = None
QMessageBox.critical(self, "Ошибка", f"Файл не найден: {self.xml_path}") QMessageBox.critical(self, "Ошибка", f"Файл не найден: {self.xml_path}")
return return
try: try:
self.update(1)
except Exception as e:
self.makefile_path = None self.makefile_path = None
self.structs_path = None self.structs_path = None
self.proj_path = None self.proj_path = None
self.update()
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}") QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}")
def delete_selected_rows(self): def delete_selected_rows(self):
selected_rows = sorted(set(index.row() for index in self.table.selectedIndexes()), reverse=True) # Получаем имена всех выбранных переменных из первого столбца
if not selected_rows: selected_names = self.table.get_selected_var_names()
if not selected_names:
return return
for row in selected_rows: # Меняем флаг show_var по имени
if 0 <= row < len(self.vars_list): for var in self.vars_list:
# Меняем флаг show_var для переменной с этим индексом if var.get('name') in selected_names:
self.vars_list[row]['show_var'] = 'false' var['show_var'] = 'false'
self.table.populate(self.vars_list, self.structs, self.write_to_xml) self.table.populate(self.vars_list, self.structs, self.write_to_xml)
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
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()
self.update() self.update()
def write_to_xml(self, dummy=None): def write_to_xml(self, dummy=None):
t0 = time.time()
self.update_all_paths() self.update_all_paths()
def get_val(name, default=''):
return str(v_table[name] if v_table and name in v_table else v.get(name, default))
def element_differs(elem, values: dict):
for tag, new_val in values.items():
current_elem = elem.find(tag)
current_val = (current_elem.text or '').strip()
new_val_str = str(new_val or '').strip()
if current_val != new_val_str:
return True
return False
if not self.xml_path or not os.path.isfile(self.xml_path): if not self.xml_path or not os.path.isfile(self.xml_path):
print("XML файл не найден или путь пустой") print("XML файл не найден или путь пустой")
return return
@@ -465,7 +539,6 @@ class VarEditor(QWidget):
if self.makefile_path and os.path.isfile(self.makefile_path): if self.makefile_path and os.path.isfile(self.makefile_path):
rel_makefile = myXML.make_relative_path(self.makefile_path, self.proj_path) rel_makefile = myXML.make_relative_path(self.makefile_path, self.proj_path)
root.set("makefile_path", rel_makefile)
# Если результат — абсолютный путь, не записываем # Если результат — абсолютный путь, не записываем
if not os.path.isabs(rel_makefile): if not os.path.isabs(rel_makefile):
root.set("makefile_path", rel_makefile) root.set("makefile_path", rel_makefile)
@@ -476,6 +549,7 @@ class VarEditor(QWidget):
if not os.path.isabs(rel_struct): if not os.path.isabs(rel_struct):
root.set("structs_path", rel_struct) root.set("structs_path", rel_struct)
t1 = time.time()
vars_elem = root.find('variables') vars_elem = root.find('variables')
if vars_elem is None: if vars_elem is None:
@@ -490,6 +564,7 @@ class VarEditor(QWidget):
'extern': var_elem.findtext('extern', ''), 'extern': var_elem.findtext('extern', ''),
'static': var_elem.findtext('static', '') 'static': var_elem.findtext('static', '')
} }
var_elements_by_name = {ve.attrib.get('name'): ve for ve in vars_elem.findall('var')}
# Читаем переменные из таблицы (активные/изменённые) # Читаем переменные из таблицы (активные/изменённые)
table_vars = {v['name']: v for v in self.table.read_data()} table_vars = {v['name']: v for v in self.table.read_data()}
@@ -498,59 +573,73 @@ class VarEditor(QWidget):
# Объединённый список переменных для записи # Объединённый список переменных для записи
all_names = list(all_vars_by_name.keys()) all_names = list(all_vars_by_name.keys())
t2 = time.time()
for name in all_names: for name in all_names:
v = all_vars_by_name[name] v = all_vars_by_name[name]
v_table = table_vars.get(name) v_table = table_vars.get(name)
var_elem = None var_elem = None
# Ищем уже существующий <var> в XML pt_type_val = get_val('pt_type').lower()
for ve in vars_elem.findall('var'): if 'arr' in pt_type_val or 'struct' in pt_type_val or 'union' in pt_type_val:
if ve.attrib.get('name') == name: continue
var_elem = ve
break
if var_elem is None:
var_elem = ET.SubElement(vars_elem, 'var', {'name': name})
def set_sub_elem_text(parent, tag, text):
el = parent.find(tag)
if el is None:
el = ET.SubElement(parent, tag)
el.text = str(text)
show_var_val = str(v.get('show_var', 'false')).lower() show_var_val = str(v.get('show_var', 'false')).lower()
enable_val = str(v_table['enable'] if v_table and 'enable' in v_table else v.get('enable', 'false')).lower() enable_val = get_val('enable').lower()
set_sub_elem_text(var_elem, 'show_var', show_var_val)
set_sub_elem_text(var_elem, 'enable', enable_val)
# Тут подтягиваем из таблицы, если есть, иначе из v # Тут подтягиваем из таблицы, если есть, иначе из v
shortname_val = v_table['shortname'] if v_table and 'shortname' in v_table else v.get('shortname', '') shortname_val = get_val('shortname')
pt_type_val = v_table['pt_type'] if v_table and 'pt_type' in v_table else v.get('pt_type', '') iq_type_val = get_val('iq_type')
iq_type_val = v_table['iq_type'] if v_table and 'iq_type' in v_table else v.get('iq_type', '') ret_type_val = get_val('return_type')
ret_type_val = v_table['return_type'] if v_table and 'return_type' in v_table else v.get('return_type', '')
set_sub_elem_text(var_elem, 'shortname', shortname_val)
set_sub_elem_text(var_elem, 'pt_type', pt_type_val)
set_sub_elem_text(var_elem, 'iq_type', iq_type_val)
set_sub_elem_text(var_elem, 'return_type', ret_type_val)
set_sub_elem_text(var_elem, 'type', v.get('type', ''))
# file/extern/static: из original_info, либо из v # file/extern/static: из original_info, либо из v
file_val = v.get('file') or original_info.get(name, {}).get('file', '') file_val = v.get('file') or original_info.get(name, {}).get('file', '')
extern_val = v.get('extern') or original_info.get(name, {}).get('extern', '') extern_val = v.get('extern') or original_info.get(name, {}).get('extern', '')
static_val = v.get('static') or original_info.get(name, {}).get('static', '') static_val = v.get('static') or original_info.get(name, {}).get('static', '')
set_sub_elem_text(var_elem, 'file', file_val) values_to_write = {
set_sub_elem_text(var_elem, 'extern', extern_val) 'show_var': show_var_val,
set_sub_elem_text(var_elem, 'static', static_val) 'enable': enable_val,
'shortname': shortname_val,
'pt_type': pt_type_val,
'iq_type': iq_type_val,
'return_type': ret_type_val,
'type': v.get('type', ''),
'file': file_val,
'extern': extern_val,
'static': static_val
}
# Ищем уже существующий <var> в XML
var_elem = var_elements_by_name.get(name)
# Если элемента нет, это новая переменная — сразу пишем
if var_elem is None:
var_elem = ET.SubElement(vars_elem, 'var', {'name': name})
var_elements_by_name[name] = var_elem
write_all = True # обязательно записать все поля
else:
write_all = element_differs(var_elem, values_to_write)
if not write_all:
continue # Пропускаем, если нет изменений
for tag, text in values_to_write.items():
set_sub_elem_text(var_elem, tag, text)
t3 = time.time()
# Преобразуем дерево в строку # Преобразуем дерево в строку
self.table.check()
myXML.fwrite(root, self.xml_path) myXML.fwrite(root, self.xml_path)
self.table.check()
t4 = time.time()
'''print(f"[T1] parse + set paths: {t1 - t0:.3f} сек")
print(f"[T2] prepare variables: {t2 - t1:.3f} сек")
print(f"[T3] loop + updates: {t3 - t2:.3f} сек")
print(f"[T4] write to file: {t4 - t3:.3f} сек")
print(f"[TOTAL] write_to_xml total: {t4 - t0:.3f} сек")'''
except Exception as e: except Exception as e:
print(f"Ошибка при сохранении XML: {e}") print(f"Ошибка при сохранении XML: {e}")
def __open_output_file_with_program(self): def __open_output_file_with_program(self):
output_path = self.get_output_path() output_path = self.get_output_path()
if not output_path: if not output_path:

55
Src/README_DEVELOP.md Normal file
View File

@@ -0,0 +1,55 @@
# Для разработчиков
### Структура проекта:
```bash
Src
├── build/
│ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller)
├── DebugVarEdit_GUI.py # Главное окно
├── var_table.py # Таблица выбранных переменных
├── var_selector_window.py # Окно выбора переменных
├── var_selector_table.py # Таблица переменных в окне выбора переменных
├── scan_progress_gui.py # Отображение процесса сканирования переменных
├── scan_vars.py # Сканирование переменных среди .c/.h файлов
├── generate_debug_vars.py # Генерация debug_vars.c
├── myXML.py # Утилиты для XML
├── makefile_parser.py # Парсинг makefile на .c/.h файлы
├── var_setup.py # Подготовка переменных для окна выбора переменных
├── libclang.dll # Бибилиотека clang
├── icon.ico # Иконка
```
### Зависимости
Для запуска приложения:
- **Python 3.7+**
- **clang** — используется для парсинга C-кода (требуется `libclang.dll`)
- **PySide2** — GUI-фреймворк
- **lxml** — работа с XML
> Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7
Для сборки `.exe`:
- **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей
- **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен)
### Сборка:
Если вы хотите собрать `DebugVarEdit.exe` самостоятельно из исходников, используйте скрипт **build/build_and_clean.py**. Он автоматически собирает проект с помощью Nuitka или PyInstaller:
- Собирает проект в `DebugVarEdit.exe` в корневой папке.
- Включает:
- все необходимые `.dll` (например, `libclang.dll`),
- иконку (`icon.ico`),
- плагины PySide2.
- Очищает временные папки после сборки:
- `build_temp`
- `__pycache__`
- `DebugVarEdit_GUI.*`
> Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`.
### Установка зависимостей
```bash
pip install PySide2 lxml nuitka pyinstaller
```

View File

@@ -1,694 +0,0 @@
import re
import xml.etree.ElementTree as ET
from PySide2.QtWidgets import (
QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton,
QLineEdit, QLabel, QHeaderView, QCompleter, QCheckBox, QHBoxLayout
)
from PySide2.QtGui import QKeySequence, QKeyEvent
from PySide2.QtCore import Qt, QStringListModel, QSettings
import VariableTable
import setupVars
import myXML
import time
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
class VariableSelectorDialog(QDialog):
def __init__(self, table, all_vars, structs, typedefs, xml_path=None, parent=None):
super().__init__(parent)
self.setWindowTitle("Выбор переменных")
self.setAttribute(Qt.WA_DeleteOnClose)
self.resize(600, 500)
self.selected_names = []
self._bckspc_pressed = False # флаг подавления добавления разделителя
self.table = table
self.all_vars = all_vars
self.structs = structs
self.typedefs = typedefs
self.expanded_vars = []
self.var_map = {v['name']: v for v in all_vars}
self.node_index = {}
self.xml_path = xml_path # сохраняем путь к xml
self.manual_completion_active = False
# --- Добавляем чекбокс для автодополнения ---
self.autocomplete_checkbox = QCheckBox("Включить автодополнение")
self.autocomplete_checkbox.setChecked(True)
# Инициализируем QSettings с именем организации и приложения
self.settings = QSettings("SET", "DebugVarEdit_VarsSelector")
# Восстанавливаем сохранённое состояние чекбокса, если есть
checked = self.settings.value("autocomplete_enabled", True, type=bool)
self.autocomplete_checkbox.setChecked(checked)
# При изменении состояния чекбокса сохраняем его
self.autocomplete_checkbox.stateChanged.connect(self.save_checkbox_state)
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Поиск по имени переменной...")
self.search_input.textChanged.connect(self.on_search_text_changed)
self.tree = QTreeWidget()
self.tree.setHeaderLabels(["Имя переменной", "Тип"])
self.tree.setSelectionMode(QTreeWidget.ExtendedSelection)
self.tree.setRootIsDecorated(True)
self.tree.setUniformRowHeights(True)
self.tree.setStyleSheet("""
QTreeWidget::item:selected {
background-color: #87CEFA;
color: black;
}
QTreeWidget::item:hover {
background-color: #D3D3D3;
}
""")
self.btn_add = QPushButton("Добавить выбранные")
self.btn_add.clicked.connect(self.on_add_clicked)
self.btn_delete = QPushButton("Удалить выбранные")
self.btn_delete.clicked.connect(self.on_delete_clicked)
self.completer = QCompleter()
self.completer.setCompletionMode(QCompleter.PopupCompletion) # важно!
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
self.completer.setFilterMode(Qt.MatchContains)
self.completer.setWidget(self.search_input)
self.search_input.installEventFilter(self)
# Создаем горизонтальный layout для "Поиск:" и чекбокса справа
search_layout = QHBoxLayout()
label_search = QLabel("Поиск:")
search_layout.addWidget(label_search, alignment=Qt.AlignLeft)
search_layout.addStretch() # чтобы чекбокс прижался вправо
search_layout.addWidget(self.autocomplete_checkbox, alignment=Qt.AlignRight)
self.completer.activated[str].connect(self.insert_completion)
layout = QVBoxLayout()
layout.addLayout(search_layout) # заменили label и чекбокс
layout.addWidget(self.search_input)
layout.addWidget(self.tree)
layout.addWidget(self.btn_add)
layout.addWidget(self.btn_delete)
self.setLayout(layout)
self.expanded_vars = setupVars.expand_vars(self.all_vars, self.structs, self.typedefs)
self.build_completion_list()
self.populate_tree()
def get_full_item_name(self, item):
names = []
while item:
names.append(item.text(0))
item = item.parent()
return '.'.join(reversed(names))
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 add_tree_item_recursively(self, parent, var):
"""
Рекурсивно добавляет переменную и её дочерние поля в дерево.
Если parent == None, добавляет на верхний уровень.
"""
name = var['name']
type_str = var.get('type', '')
show_var = var.get('show_var', 'false') == 'true'
item = QTreeWidgetItem([name, type_str])
item.setData(0, Qt.UserRole, name)
full_name = self.get_full_item_name(item)
self.node_index[full_name.lower()] = item
# Делаем bitfield-поля неактивными
if "(bitfield:" in type_str:
item.setDisabled(True)
self.set_tool(item, "Битовые поля недоступны для выбора")
for i, attr in enumerate(['file', 'extern', 'static']):
item.setData(0, Qt.UserRole + 1 + i, var.get(attr))
if show_var:
item.setForeground(0, Qt.gray)
item.setForeground(1, Qt.gray)
self.set_tool(item, "Уже добавлена")
if parent is None:
self.tree.addTopLevelItem(item)
else:
parent.addChild(item)
for child in var.get('children', []):
self.add_tree_item_recursively(item, child)
def populate_tree(self, vars_list=None):
if vars_list is None:
vars_list = self.expanded_vars
self.tree.clear()
self.node_index.clear()
for var in vars_list:
self.add_tree_item_recursively(None, var)
header = self.tree.header()
header.setSectionResizeMode(QHeaderView.Interactive) # вручную можно менять
self.tree.setColumnWidth(0, 400)
self.tree.resizeColumnToContents(1)
def expand_to_level(self, item, level, current_level=0):
"""
Рекурсивно раскрывает узлы до заданного уровня.
"""
if current_level < level:
item.setExpanded(True)
else:
item.setExpanded(False)
for i in range(item.childCount()):
self.expand_to_level(item.child(i), level, current_level + 1)
def filter_tree(self):
text = self.search_input.text().strip().lower()
path_parts = text.split('.') if text else []
filtered_vars = filter_vars(self.expanded_vars, path_parts)
# Сначала перерисовываем дерево
self.populate_tree(filtered_vars)
# Теперь node_index уже пересоздан — можно работать
expand_level = len(path_parts) - 1 if path_parts else 0
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
self.expand_to_level(item, expand_level)
# Раскрываем путь до точного совпадения
if path_parts:
fullname = '.'.join(path_parts)
node = self.node_index.get(fullname.lower())
if node:
parent = node.parent()
while parent:
parent.setExpanded(True)
parent = parent.parent()
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:
text = self.search_input.text().strip()
else:
text = text.strip()
parts = self.split_path(text)
path_parts = parts[:-1] if parts else []
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)
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:
completions.extend(node.child(i).text(0) for i in range(node.childCount()))
elif not path_parts:
# Первый уровень — только если имя начинается с prefix
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
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)
name = child.text(0)
name_parts = child.data(0, Qt.UserRole + 10)
if name_parts is None:
name_parts = self.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 last_part.startswith(prefix): # ← строго startswith
completions.append(name)
self.completer.setModel(QStringListModel(completions))
self.completer.complete()
return completions
# Функция для поиска узла с полным именем
def find_node_by_fullname(self, name):
return self.node_index.get(name.lower())
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.setCursorPosition(len(text))
def eventFilter(self, obj, event):
if obj == self.search_input and isinstance(event, QKeyEvent):
if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier:
self.manual_completion_active = True
text = self.search_input.text().strip()
self.run_completions(text)
elif event.key() == Qt.Key_Escape:
# Esc — выключаем ручной режим и скрываем подсказки, если autocomplete выключен
if not self.autocomplete_checkbox.isChecked():
self.manual_completion_active = False
self.completer.popup().hide()
return True
if event.key() == Qt.Key_Backspace:
self._bckspc_pressed = True
else:
self._bckspc_pressed = False
return super().eventFilter(obj, event)
def run_completions(self, text):
completions = self.update_completions(text)
if not self.autocomplete_checkbox.isChecked() and self._bckspc_pressed:
text = text[:-1]
if len(completions) == 1 and completions[0].lower() == text.lower():
# Найдем узел с таким именем
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])
if node and node.childCount() > 0:
# Используем первую подсказку, чтобы определить нужный разделитель
completions = self.update_completions(text + '.')
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):
self.filter_tree()
if text == None:
text = self.search_input.text().strip()
if self.autocomplete_checkbox.isChecked():
self.run_completions(text)
else:
# Если выключено, показываем подсказки только если флаг ручного вызова True
if self.manual_completion_active:
self.run_completions(text)
else:
self.completer.popup().hide()
def on_add_clicked(self):
self.selected_names = []
self.tree.setFocus()
for item in self.tree.selectedItems():
name = item.text(0) # имя переменной (в колонке 1)
type_str = item.text(1) # тип переменной (в колонке 2)
if not name:
continue
self.selected_names.append((name, type_str))
if name in self.var_map:
# Если переменная уже есть, просто включаем её и показываем
var = self.var_map[name]
var['show_var'] = 'true'
var['enable'] = 'true'
else:
# Создаём новый элемент переменной
# Получаем родительские параметры
file_val = item.data(0, Qt.UserRole + 1)
extern_val = item.data(0, Qt.UserRole + 2)
static_val = item.data(0, Qt.UserRole + 3)
new_var = {
'name': name,
'type': type_str,
'show_var': 'true',
'enable': 'true',
'shortname': name,
'pt_type': '',
'iq_type': '',
'return_type': 'iq_none',
'file': file_val,
'extern': str(extern_val).lower() if extern_val else 'false',
'static': str(static_val).lower() if static_val else 'false',
}
# Добавляем в список переменных
self.all_vars.append(new_var)
self.var_map[name] = new_var # Чтобы в будущем не добавлялось повторно
self.done(QDialog.Accepted)
def on_delete_clicked(self):
selected_names = self._get_selected_var_names()
if not selected_names:
print("nothing selected")
return
# Обновляем var_map и all_vars
for name in selected_names:
if name in self.var_map:
self.var_map[name]['show_var'] = 'false'
self.var_map[name]['enable'] = 'false'
for v in self.all_vars:
if v['name'] == name:
v['show_var'] = 'false'
v['enable'] = 'false'
break
# Проверка пути к XML
if not hasattr(self, 'xml_path') or not self.xml_path:
from PySide2.QtWidgets import QMessageBox
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
return
root, tree = myXML.safe_parse_xml(self.xml_path)
if root is None:
return
vars_section = root.find('variables')
if vars_section is None:
return
for var_elem in vars_section.findall('var'):
name = var_elem.attrib.get('name')
if name in selected_names:
def set_text(tag, value):
el = var_elem.find(tag)
if el is None:
el = ET.SubElement(var_elem, tag)
el.text = value
set_text('show_var', 'false')
set_text('enable', 'false')
myXML.fwrite(root, self.xml_path)
self.done(QDialog.Accepted)
def set_tool(self, item, text):
item.setToolTip(0, text)
item.setToolTip(1, text)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Delete:
self.delete_selected_vars()
else:
super().keyPressEvent(event)
def delete_selected_vars(self):
selected_names = self._get_selected_var_names()
if not selected_names:
print("nothing selected")
return
# Обновляем var_map и all_vars
for name in selected_names:
if name in self.var_map:
self.var_map[name]['show_var'] = 'false'
self.var_map[name]['enable'] = 'false'
for v in self.all_vars:
if v['name'] == name:
v['show_var'] = 'false'
v['enable'] = 'false'
break
# Проверка пути к XML
if not hasattr(self, 'xml_path') or not self.xml_path:
from PySide2.QtWidgets import QMessageBox
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
return
root, tree = myXML.safe_parse_xml(self.xml_path)
if root is None:
return
vars_section = root.find('variables')
if vars_section is None:
return
for var_elem in vars_section.findall('var'):
name = var_elem.attrib.get('name')
if name in selected_names:
def set_text(tag, value):
el = var_elem.find(tag)
if el is None:
el = ET.SubElement(var_elem, tag)
el.text = value
set_text('show_var', 'false')
set_text('enable', 'false')
myXML.fwrite(root, self.xml_path)
self.table.populate(self.all_vars, self.structs, None)
# Проверка пути к XML
if not hasattr(self, 'xml_path') or not self.xml_path:
from PySide2.QtWidgets import QMessageBox
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно удалить переменные.")
return
import xml.etree.ElementTree as ET
root, tree = myXML.safe_parse_xml(self.xml_path)
if root is None:
return
vars_section = root.find('variables')
if vars_section is None:
return
removed_any = False
for var_elem in list(vars_section.findall('var')):
name = var_elem.attrib.get('name')
if name in selected_names:
vars_section.remove(var_elem)
removed_any = True
self.var_map.pop(name, None)
# Удаляем из all_vars (глобально)
self.all_vars[:] = [v for v in self.all_vars if v['name'] not in selected_names]
# Удаляем из expanded_vars (тоже глобально)
def filter_out_selected(vars_list):
filtered = []
for v in vars_list:
if v['name'] not in selected_names:
# Рекурсивно фильтруем детей, если есть
if 'children' in v:
v = v.copy()
v['children'] = filter_out_selected(v['children'])
filtered.append(v)
return filtered
self.expanded_vars[:] = filter_out_selected(self.expanded_vars)
if removed_any:
myXML.fwrite(root, self.xml_path)
self.filter_tree()
def _get_selected_var_names(self):
self.tree.setFocus()
return [item.text(0) for item in self.tree.selectedItems() if item.text(0)]
def save_checkbox_state(self):
self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked())
def split_path(self, path):
"""
Разбивает путь на компоненты:
- 'foo[2].bar[1]->baz' → ['foo', [2]', 'bar', '[1]' 'baz']
"""
tokens = []
token = ''
i = 0
while i < len(path):
c = path[i]
# Разделители: '->' и '.'
if c == '-' and path[i:i+2] == '->':
if token:
tokens.append(token)
token = ''
i += 2
continue
elif c == '.':
if token:
tokens.append(token)
token = ''
i += 1
continue
elif c == '[':
# Заканчиваем текущий токен, если есть
if token:
tokens.append(token)
token = ''
# Собираем индекс [N]
idx = ''
while i < len(path) and path[i] != ']':
idx += path[i]
i += 1
if i < len(path) and path[i] == ']':
idx += ']'
i += 1
tokens.append(idx)
continue
else:
token += c
i += 1
if token:
tokens.append(token)
return tokens
def filter_vars(vars_list, path_parts):
"""Рекурсивно фильтруем vars_list по path_parts по вхождению на любом уровне."""
filtered = []
def matches_path(name, search_parts):
name_parts = name.lower().split('.')
if len(name_parts) < len(search_parts):
return False
for sp, np in zip(search_parts, name_parts):
if sp not in np:
return False
return True
for var in vars_list:
fullname = var.get('fullname', var['name']) # желательно иметь полное имя
if not path_parts or matches_path(fullname, path_parts):
new_var = var.copy()
if 'children' in var:
new_var['children'] = filter_vars(var['children'], path_parts)
filtered.append(new_var)
else:
if 'children' in var:
child_filtered = filter_vars(var['children'], path_parts)
if child_filtered:
new_var = var.copy()
new_var['children'] = child_filtered
filtered.append(new_var)
return filtered

View File

@@ -1,271 +0,0 @@
from PySide2.QtWidgets import (
QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter,
QAbstractItemView, QHeaderView, QLabel
)
from PySide2.QtGui import QColor, QBrush, QPalette
from PySide2.QtCore import Qt
from enum import IntEnum
from generateVars import type_map
class rows(IntEnum):
No = 0
include = 1
name = 2
type = 3
pt_type = 4
iq_type = 5
ret_type = 6
short_name = 7
class VariableTableWidget(QTableWidget):
def __init__(self, parent=None):
super().__init__(0, 8, parent)
# Таблица переменных
self.setHorizontalHeaderLabels([
'', # новый столбец
'En',
'Name',
'Origin Type',
'Pointer Type',
'IQ Type',
'Return Type',
'Short Name'
])
self.setEditTriggers(QAbstractItemView.AllEditTriggers)
self.var_list = []
self.type_options = list(dict.fromkeys(type_map.values()))
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
self.iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
header = self.horizontalHeader()
# Для остальных колонок — растяжение (Stretch), чтобы они заняли всю оставшуюся ширину
for col in range(self.columnCount()):
if col == self.columnCount() - 1:
header.setSectionResizeMode(col, QHeaderView.Stretch)
else:
header.setSectionResizeMode(col, QHeaderView.Interactive)
parent_widget = self.parentWidget()
# Сделаем колонки с номерами фиксированной ширины
self.setColumnWidth(rows.No, 30)
self.setColumnWidth(rows.include, 30)
self.setColumnWidth(rows.pt_type, 85)
self.setColumnWidth(rows.iq_type, 85)
self.setColumnWidth(rows.ret_type, 85)
self.setColumnWidth(rows.name, 300)
self.setColumnWidth(rows.type, 100)
self._resizing = False
self.horizontalHeader().sectionResized.connect(self.on_section_resized)
def populate(self, vars_list, structs, on_change_callback):
self.var_list = vars_list
self.type_options = list(dict.fromkeys(type_map.values()))
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
# --- ДО: удаляем отображение структур и union-переменных
for var in vars_list:
pt_type = var.get('pt_type', '')
if 'struct' in pt_type or 'union' in pt_type:
var['show_var'] = 'false'
var['enable'] = 'false'
filtered_vars = [v for v in vars_list if v.get('show_var', 'false') == 'true']
self.setRowCount(len(filtered_vars))
self.verticalHeader().setVisible(False)
style_with_padding = "padding-left: 5px; padding-right: 5px; font-size: 14pt; font-family: 'Segoe UI';"
for row, var in enumerate(filtered_vars):
# №
no_item = QTableWidgetItem(str(row))
no_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
self.setItem(row, rows.No, no_item)
# Enable
cb = QCheckBox()
cb.setChecked(var.get('enable', 'false') == 'true')
cb.stateChanged.connect(on_change_callback)
cb.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.include, cb)
# 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.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.name, name_edit)
# Origin Type (readonly)
origin_item = QTableWidgetItem(var.get('type', ''))
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
origin_item.setToolTip(var.get('type', '')) # Всплывающая подсказка
origin_item.setForeground(QBrush(Qt.black))
self.setItem(row, rows.type, origin_item)
# pt_type
pt_combo = QComboBox()
pt_combo.addItems(self.display_type_options)
value = var['pt_type'].replace('pt_', '')
if value not in self.display_type_options:
pt_combo.addItem(value)
pt_combo.setCurrentText(value)
pt_combo.currentTextChanged.connect(on_change_callback)
pt_combo.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.pt_type, pt_combo)
# iq_type
iq_combo = QComboBox()
iq_combo.addItems(self.iq_types)
value = var['iq_type'].replace('t_', '')
if value not in self.iq_types:
iq_combo.addItem(value)
iq_combo.setCurrentText(value)
iq_combo.currentTextChanged.connect(on_change_callback)
iq_combo.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.iq_type, iq_combo)
# return_type
ret_combo = QComboBox()
ret_combo.addItems(self.iq_types)
ret_combo.setCurrentText(var.get('return_type', ''))
ret_combo.currentTextChanged.connect(on_change_callback)
ret_combo.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.ret_type, ret_combo)
# short_name
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.check()
def check(self):
warning_color = (QColor("#FFFACD")) # Жёлтый для длинных shortname
error_color = (QColor("#FFB6C1")) # Светло-красный для отсутствующих переменных
tooltip_shortname = "Short Name длиннее 10 символов — будет обрезано при генерации"
tooltip_missing = f'Имя переменной не найдено среди переменных. Добавьте её через кнопку "Добавить переменные"'
for row in range(self.rowCount()):
# Получаем имя переменной (столбец `name`)
name_widget = self.cellWidget(row, rows.name)
name = name_widget.text() if name_widget else ""
# Получаем shortname
short_name_edit = self.cellWidget(row, rows.short_name)
shortname = short_name_edit.text() if short_name_edit else ""
# Флаги ошибок
long_shortname = len(shortname) > 10
found = any(v.get('name') == name for v in self.var_list)
# Выбираем цвет и подсказку
color = None
tooltip = ""
if not found:
color = error_color
tooltip = tooltip_missing
elif long_shortname:
color = warning_color
tooltip = tooltip_shortname
self.highlight_row(row, color, tooltip)
def read_data(self):
result = []
for row in range(self.rowCount()):
cb = self.cellWidget(row, rows.include)
name = self.cellWidget(row, rows.name).text()
pt = self.cellWidget(row, rows.pt_type).currentText()
iq = self.cellWidget(row, rows.iq_type).currentText()
ret = self.cellWidget(row, rows.ret_type).currentText()
shortname = self.cellWidget(row, rows.short_name).text()
origin_type = self.item(row, rows.type).text()
result.append({
'show_var': True,
'enable': cb.isChecked(),
'name': name,
'pt_type': f'pt_{pt}',
'iq_type': f't_{iq}',
'return_type': f't_{ret}',
'shortname': shortname,
'type': origin_type,
})
return result
def on_section_resized(self, logicalIndex, oldSize, newSize):
if self._resizing:
return # предотвращаем рекурсию
min_width = 50
delta = newSize - oldSize
right_index = logicalIndex + 1
if right_index >= self.columnCount():
# Если правая колока - нет соседа, ограничиваем минимальную ширину
if newSize < min_width:
self._resizing = True
self.setColumnWidth(logicalIndex, min_width)
self._resizing = False
return
self._resizing = True
try:
right_width = self.columnWidth(right_index)
new_right_width = right_width - delta
# Если соседняя колонка станет уже минимальной - подкорректируем левую
if new_right_width < min_width:
new_right_width = min_width
newSize = oldSize + (right_width - min_width)
self.setColumnWidth(logicalIndex, newSize)
self.setColumnWidth(right_index, new_right_width)
finally:
self._resizing = False
def highlight_row(self, row: int, color: QColor = None, tooltip: str = ""):
"""
Подсвечивает строку таблицы цветом `color`, не меняя шрифт.
Работает с QLineEdit, QComboBox, QCheckBox (включая обёртки).
Если `color=None`, сбрасывает подсветку.
"""
css_reset = "background-color: none; font: inherit;"
css_color = f"background-color: {color.name()};" if color else css_reset
for col in range(self.columnCount()):
item = self.item(row, col)
widget = self.cellWidget(row, col)
# Подсветка обычной item-ячейки (например, тип переменной)
if item is not None:
if color:
item.setBackground(QBrush(color))
item.setToolTip(tooltip)
else:
item.setBackground(QBrush(Qt.NoBrush))
item.setToolTip("")
# Подсветка виджетов — здесь главная доработка
elif widget is not None:
# Надёжная подсветка: через styleSheet
widget.setStyleSheet(css_color)
widget.setToolTip(tooltip if color else "")

View File

@@ -2,13 +2,11 @@ import subprocess
import shutil import shutil
import os import os
from pathlib import Path from pathlib import Path
from PIL import Image # нужна библиотека Pillow для конвертации PNG в ICO
import PySide2 import PySide2
from PyInstaller.utils.hooks import collect_data_files from PyInstaller.utils.hooks import collect_data_files
# install: # install: pip install PySide2 lxml nuitka pyinstaller
# - PyInstaller # - PyInstaller
# - nuitka # - nuitka
# - Pillow
# - PySide2 # - PySide2
# - clang # - clang
@@ -25,6 +23,13 @@ SPEC_PATH = WORK_PATH
ICON_PATH = SRC_PATH / "icon.png" ICON_PATH = SRC_PATH / "icon.png"
ICON_ICO_PATH = SRC_PATH / "icon.ico" ICON_ICO_PATH = SRC_PATH / "icon.ico"
TEMP_FOLDERS = [
"build_temp",
"__pycache__",
"DebugVarEdit_GUI.build",
"DebugVarEdit_GUI.onefile-build",
"DebugVarEdit_GUI.dist"
]
# === Пути к DLL и прочим зависимостям === # === Пути к DLL и прочим зависимостям ===
LIBS = { LIBS = {
"libclang.dll": SRC_PATH / "libclang.dll" "libclang.dll": SRC_PATH / "libclang.dll"
@@ -48,7 +53,7 @@ for name, path in LIBS.items():
print(f"WARNING: {path.name} не найден — он не будет включён в сборку") print(f"WARNING: {path.name} не найден — он не будет включён в сборку")
def clean_temp(): def clean_temp():
for folder in ["build_temp", "__pycache__", "DebugVarEdit_GUI.build", "DebugVarEdit_GUI.dist", "DebugVarEdit_GUI.onefile-build"]: for folder in TEMP_FOLDERS:
path = Path(folder) path = Path(folder)
if path.exists(): if path.exists():
shutil.rmtree(path, ignore_errors=True) shutil.rmtree(path, ignore_errors=True)

View File

@@ -1,20 +1,21 @@
# build command # build command
# pyinstaller --onefile --distpath . --workpath ./build --specpath ./build generateVars.py # pyinstaller --onefile --distpath . --workpath ./build --specpath ./build generate_debug_vars.py
# start script # start script
# generateVars.exe F:\Work\Projects\TMS\TMS_new_bus\ Src/DebugTools/vars.xml Src/DebugTools # generate_debug_vars.exe F:\Work\Projects\TMS\TMS_new_bus\ Src/DebugTools/vars.xml Src/DebugTools
import sys import sys
import os import os
import re import re
import xml.etree.ElementTree as ET import lxml.etree as ET
from pathlib import Path from pathlib import Path
from xml.dom import minidom 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:<45} {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)

242
Src/makefile_parser.py Normal file
View File

@@ -0,0 +1,242 @@
import os
import re
from lxml import etree as ET
def strip_single_line_comments(code):
# Удалим // ... до конца строки
return re.sub(r'//.*?$', '', code, flags=re.MULTILINE)
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()
content = strip_single_line_comments(content)
return content, enc
except UnicodeDecodeError:
continue
raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251")
def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
"""
Рекурсивно ищет все include-файлы начиная с заданных c_files.
Возвращает множество ПОЛНЫХ ПУТЕЙ к найденным include-файлам.
include_dirs — список директорий, в которых ищем include-файлы.
processed_files — множество уже обработанных файлов (для избежания циклов).
"""
if processed_files is None:
processed_files = set()
include_files = set()
include_pattern = re.compile(r'#include\s+"([^"]+)"')
for cfile in c_files:
norm_path = os.path.normpath(cfile)
if norm_path in processed_files:
continue
processed_files.add(norm_path)
content, _ = read_file_try_encodings(cfile)
if content is None:
continue
includes = include_pattern.findall(content)
for inc in includes:
# Ищем полный путь к include-файлу в include_dirs
inc_full_path = None
for dir_ in include_dirs:
candidate = os.path.normpath(os.path.join(dir_, inc))
if os.path.isfile(candidate):
inc_full_path = os.path.abspath(candidate)
break
if inc_full_path:
include_files.add(inc_full_path)
# Рекурсивный обход вложенных includes
if inc_full_path not in processed_files:
nested_includes = find_all_includes_recursive(
[inc_full_path], include_dirs, processed_files
)
include_files.update(nested_includes)
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):
import os
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:
lines = f.readlines()
raw_entries = []
collecting = False
for line in lines:
stripped = line.strip()
if (("ORDERED_OBJS" in stripped or "C_SOURCES" in stripped) and ("+=" in stripped or "=" in stripped)):
collecting = True
if collecting:
line_clean = stripped.rstrip("\\").strip()
if line_clean:
line_clean = re.sub(r"\$\([^)]+\)", "", line_clean)
line_clean = re.sub(r"\$\{[^}]+\}", "", line_clean)
raw_entries.append(line_clean)
if not stripped.endswith("\\"):
collecting = False
for entry in raw_entries:
for token in entry.split():
token = token.strip('"')
if not token:
continue
token = token.replace("\\", "/")
if token.endswith(".obj"):
token = re.sub(r"\.obj$", ".c", token)
elif token.endswith(".o"):
token = re.sub(r"\.o$", ".c", token)
if token.endswith(".c"):
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))
if not c_files:
makefile_dir = os.path.dirname(os.path.abspath(makefile_path))
objects_list_path = os.path.join(makefile_dir, "objects.list")
c_from_objects, inc_from_objects = parse_objects_list(objects_list_path, project_root)
c_files.extend(c_from_objects)
include_dirs.update(inc_from_objects)
for line in lines:
if "-I" in line or "C_INCLUDES" in line:
matches = re.findall(r"-I\s*([^\s\\]+)", line)
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)
# Добавляем пути с заменой 'Src' на 'Inc', если путь заканчивается на 'Src'
additional_includes = set()
for inc in include_dirs:
if inc.endswith(os.sep + "Src") or inc.endswith("/Src"):
inc_inc = inc[:-3] + "Inc" # заменяем 'Src' на 'Inc'
if os.path.isdir(inc_inc):
additional_includes.add(inc_inc)
include_dirs.update(additional_includes)
h_files = find_all_includes_recursive(c_files, include_dirs)
return sorted(c_files), sorted(h_files), sorted(include_dirs), sorted(defines)
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)

View File

@@ -1,5 +1,5 @@
import os import os
import xml.etree.ElementTree as ET from lxml import etree
def make_absolute_path(path, base_path): def make_absolute_path(path, base_path):
if not os.path.isabs(path) and os.path.isdir(base_path): if not os.path.isabs(path) and os.path.isdir(base_path):
@@ -33,44 +33,38 @@ def make_relative_path(abs_path, base_path):
return abs_path.replace("\\", "/") return abs_path.replace("\\", "/")
def indent_xml(elem, level=0): def indent_xml(elem, level=0):
indent = " " # Убираем strip() — они медленные, и текст мы всё равно перезаписываем
i = "\n" + level * indent i = "\n" + level * " "
if len(elem): if len(elem):
if not elem.text or not elem.text.strip(): elem.text = elem.text or i + " "
elem.text = i + indent
for child in elem: for child in elem:
indent_xml(child, level + 1) indent_xml(child, level + 1)
if not elem.tail or not elem.tail.strip(): elem[-1].tail = elem[-1].tail or i
elem.tail = i
else: else:
if level and (not elem.tail or not elem.tail.strip()): elem.tail = elem.tail or i
elem.tail = i
def fwrite(root, xml_full_path): def fwrite(root, xml_full_path):
indent_xml(root) #indent_xml(root)
ET.ElementTree(root).write(xml_full_path, encoding="utf-8", xml_declaration=True) #ET.ElementTree(root).write(xml_full_path, encoding="utf-8", xml_declaration=True)
rough_string = etree.tostring(root, encoding="utf-8")
parsed = etree.fromstring(rough_string)
with open(xml_full_path, "wb") as f:
f.write(etree.tostring(parsed, pretty_print=True, encoding="utf-8", xml_declaration=True))
def safe_parse_xml(xml_path):
"""
Безопасно парсит XML-файл.
Возвращает кортеж (root, tree) или (None, None) при ошибках. def safe_parse_xml(xml_path):
"""
if not xml_path or not os.path.isfile(xml_path): if not xml_path or not os.path.isfile(xml_path):
print(f"Файл '{xml_path}' не найден или путь пустой") print(f"Файл '{xml_path}' не найден или путь пустой")
return None, None return None, None
try: try:
if os.path.getsize(xml_path) == 0: if os.path.getsize(xml_path) == 0:
return None, None return None, None
tree = etree.parse(xml_path)
tree = ET.parse(xml_path)
root = tree.getroot() root = tree.getroot()
return root, tree return root, tree
except etree .XMLSyntaxError as e:
except ET.ParseError as e:
print(f"Ошибка парсинга XML файла '{xml_path}': {e}") print(f"Ошибка парсинга XML файла '{xml_path}': {e}")
return None, None return None, None
except Exception as e: except Exception as e:

View File

@@ -1,154 +0,0 @@
import os
import re
def strip_single_line_comments(code):
# Удалим // ... до конца строки
return re.sub(r'//.*?$', '', code, flags=re.MULTILINE)
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()
content = strip_single_line_comments(content)
return content, enc
except UnicodeDecodeError:
continue
raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251")
def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
"""
Рекурсивно ищет все include-файлы начиная с заданных c_files.
Возвращает множество ПОЛНЫХ ПУТЕЙ к найденным include-файлам.
include_dirs — список директорий, в которых ищем include-файлы.
processed_files — множество уже обработанных файлов (для избежания циклов).
"""
if processed_files is None:
processed_files = set()
include_files = set()
include_pattern = re.compile(r'#include\s+"([^"]+)"')
for cfile in c_files:
norm_path = os.path.normpath(cfile)
if norm_path in processed_files:
continue
processed_files.add(norm_path)
content, _ = read_file_try_encodings(cfile)
if content is None:
continue
includes = include_pattern.findall(content)
for inc in includes:
# Ищем полный путь к include-файлу в include_dirs
inc_full_path = None
for dir_ in include_dirs:
candidate = os.path.normpath(os.path.join(dir_, inc))
if os.path.isfile(candidate):
inc_full_path = os.path.abspath(candidate)
break
if inc_full_path:
include_files.add(inc_full_path)
# Рекурсивный обход вложенных includes
if inc_full_path not in processed_files:
nested_includes = find_all_includes_recursive(
[inc_full_path], include_dirs, processed_files
)
include_files.update(nested_includes)
return include_files
def parse_makefile(makefile_path, proj_path):
makefile_dir = os.path.dirname(makefile_path)
project_root = proj_path
with open(makefile_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
objs_lines = []
collecting = False
for line in lines:
stripped = line.strip()
if stripped.startswith("ORDERED_OBJS") and "+=" in stripped:
parts = stripped.split("\\")
first_part = parts[0]
idx = first_part.find("+=")
tail = first_part[idx+2:].strip()
if tail:
objs_lines.append(tail)
collecting = True
if len(parts) > 1:
for p in parts[1:]:
p = p.strip()
if p:
objs_lines.append(p)
continue
if collecting:
if stripped.endswith("\\"):
objs_lines.append(stripped[:-1].strip())
else:
objs_lines.append(stripped)
collecting = False
objs_str = ' '.join(objs_lines)
objs_str = re.sub(r"\$\([^)]+\)", "", objs_str)
objs = []
for part in objs_str.split():
part = part.strip()
if part.startswith('"') and part.endswith('"'):
part = part[1:-1]
if part:
objs.append(part)
c_files = []
include_dirs = set()
for obj_path in objs:
if "DebugTools" in obj_path:
continue
if "v120" in obj_path:
continue
if "v100" in obj_path:
continue
if obj_path.startswith("Debug\\") or obj_path.startswith("Debug/"):
rel_path = obj_path.replace("Debug\\", "Src\\").replace("Debug/", "Src/")
else:
rel_path = obj_path
abs_path = os.path.normpath(os.path.join(project_root, rel_path))
root, ext = os.path.splitext(abs_path)
if ext.lower() == ".obj":
c_path = root + ".c"
else:
c_path = abs_path
# Проверяем существование файла, если нет — пропускаем
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)
h_files = find_all_includes_recursive(c_files, include_dirs)
return sorted(c_files), sorted(h_files), sorted(include_dirs)

View File

@@ -6,8 +6,8 @@ import sys
import contextlib import contextlib
import io import io
import json import json
from scanVars import run_scan from scan_vars import run_scan
from VariableTable import VariableTableWidget, rows from var_table import VariableTableWidget, rows
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem, QApplication, QWidget, QTableWidget, QTableWidgetItem,
@@ -73,13 +73,13 @@ class EmittingStream(QObject):
self._buffer = "" self._buffer = ""
class ProcessOutputWindow(QDialog): class ProcessOutputWindow(QDialog):
def __init__(self, proj_path, makefile_path, xml_path, on_done_callback=None): def __init__(self, proj_path, makefile_path, xml_path, on_done_callback=None, parent=None):
super().__init__() super().__init__(parent)
self.setWindowTitle("Поиск переменных...") self.setWindowTitle("Поиск переменных...")
self.resize(600, 480) self.resize(600, 480)
self.setModal(True) self.setModal(True)
self.setAttribute(Qt.WA_DeleteOnClose)
self.proj_path = proj_path self.proj_path = proj_path
self.makefile_path = makefile_path self.makefile_path = makefile_path

View File

@@ -1,7 +1,7 @@
# build command # build command
# pyinstaller --onefile scanVars.py --add-binary "F:\Work\Projects\TMS\TMS_new_bus\Src\DebugTools/build/libclang.dll;." --distpath . --workpath ./build --specpath ./build # pyinstaller --onefile scan_vars.py --add-binary "F:\Work\Projects\TMS\TMS_new_bus\Src\DebugTools/build/libclang.dll;." --distpath . --workpath ./build --specpath ./build
# start script # start script
# scanVars.exe F:\Work\Projects\TMS\TMS_new_bus\ F:\Work\Projects\TMS\TMS_new_bus\Debug\makefile # scan_vars.exe F:\Work\Projects\TMS\TMS_new_bus\ F:\Work\Projects\TMS\TMS_new_bus\Debug\makefile
import os import os
import sys import sys
@@ -9,9 +9,9 @@ import re
import clang.cindex import clang.cindex
from clang import cindex from clang import cindex
from clang.cindex import Config from clang.cindex import Config
import xml.etree.ElementTree as ET import lxml.etree as ET
from xml.dom import minidom from xml.dom import minidom
from parseMakefile 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}"
@@ -545,7 +570,7 @@ def read_vars_from_xml(xml_path):
'shortname': var_elem.findtext('shortname', name), 'shortname': var_elem.findtext('shortname', name),
'pt_type': var_elem.findtext('pt_type', ''), 'pt_type': var_elem.findtext('pt_type', ''),
'iq_type': var_elem.findtext('iq_type', ''), 'iq_type': var_elem.findtext('iq_type', ''),
'return_type': var_elem.findtext('return_type', 'int'), 'return_type': var_elem.findtext('return_type', 't_iq_none'),
'type': var_elem.findtext('type', 'unknown'), 'type': var_elem.findtext('type', 'unknown'),
'file': var_elem.findtext('file', ''), 'file': var_elem.findtext('file', ''),
'extern': get_bool('extern'), 'extern': get_bool('extern'),
@@ -608,7 +633,7 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
'shortname': info.get('shortname', name), 'shortname': info.get('shortname', name),
'pt_type': info.get('pt_type', ''), 'pt_type': info.get('pt_type', ''),
'iq_type': info.get('iq_type', ''), 'iq_type': info.get('iq_type', ''),
'return_type': info.get('return_type', 'int'), 'return_type': info.get('return_type', 't_iq_none'),
'type': info.get('type', 'unknown'), 'type': info.get('type', 'unknown'),
'file': info.get('file', ''), 'file': info.get('file', ''),
'extern': info.get('extern', False), 'extern': info.get('extern', False),
@@ -627,7 +652,7 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
ET.SubElement(var_elem, "shortname").text = info.get('shortname', name) ET.SubElement(var_elem, "shortname").text = info.get('shortname', name)
ET.SubElement(var_elem, "pt_type").text = info.get('pt_type', '') ET.SubElement(var_elem, "pt_type").text = info.get('pt_type', '')
ET.SubElement(var_elem, "iq_type").text = info.get('iq_type', '') ET.SubElement(var_elem, "iq_type").text = info.get('iq_type', '')
ET.SubElement(var_elem, "return_type").text = info.get('return_type', 'int') ET.SubElement(var_elem, "return_type").text = info.get('return_type', 't_iq_none')
ET.SubElement(var_elem, "type").text = info.get('type', 'unknown') ET.SubElement(var_elem, "type").text = info.get('type', 'unknown')
rel_file = make_relative_if_possible(info.get('file', ''), proj_path).replace("\\", "/") rel_file = make_relative_if_possible(info.get('file', ''), proj_path).replace("\\", "/")
@@ -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)

606
Src/var_selector_table.py Normal file
View File

@@ -0,0 +1,606 @@
import re
from PySide2.QtWidgets import (
QWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLineEdit,
QHeaderView, QCompleter
)
from PySide2.QtGui import QKeyEvent
from PySide2.QtCore import Qt, QStringListModel
import pickle
import time
import hashlib
def compute_vars_hash(vars_list):
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):
return item.childCount() == 1 and item.child(0).text(0) == 'lazy_marker'
class VariableSelectWidget(QWidget):
def __init__(self, parent=None):
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.search_input = QLineEdit(self)
self.search_input.setPlaceholderText("Поиск...")
self.tree = QTreeWidget(self)
self.tree.setHeaderLabels(["Имя переменной", "Тип"])
self.tree.setSelectionMode(QTreeWidget.ExtendedSelection)
self.tree.setRootIsDecorated(True)
self.tree.setUniformRowHeights(True)
self.tree.setStyleSheet("""
QTreeWidget::item:selected { background-color: #87CEFA; color: black; }
QTreeWidget::item:hover { background-color: #D3D3D3; }
""")
self.tree.itemExpanded.connect(self.on_item_expanded)
self.completer = QCompleter(self)
self.completer.setCompletionMode(QCompleter.PopupCompletion)
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
self.completer.setFilterMode(Qt.MatchContains)
self.completer.setWidget(self.search_input)
# --- Layout ---
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.search_input)
layout.addWidget(self.tree)
# --- Соединения ---
#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.completer.activated[str].connect(lambda text: self.insert_completion(text))
# --- Публичные методы для управления виджетом снаружи ---
def set_autocomplete(self, enabled: bool):
"""Включает или выключает режим автодополнения."""
self.is_autocomplete_on = enabled
def set_data(self, vars_list):
"""Основной метод для загрузки данных в виджет."""
self.expanded_vars = pickle.loads(pickle.dumps(vars_list, protocol=pickle.HIGHEST_PROTOCOL))
# self.build_completion_list() # Если нужна полная перестройка списка
self.populate_tree()
def populate_tree(self, vars_list=None):
if vars_list is None:
vars_list = self.expanded_vars
new_hash = compute_vars_hash(vars_list)
if self._vars_hash == new_hash:
return
self._vars_hash = new_hash
self.tree.setUpdatesEnabled(False)
self.tree.blockSignals(True)
self.tree.clear()
self.node_index.clear()
for var in vars_list:
self.add_tree_item_lazy(None, var)
self.tree.setUpdatesEnabled(True)
self.tree.blockSignals(False)
header = self.tree.header()
header.setSectionResizeMode(QHeaderView.Interactive)
header.setSectionResizeMode(1, QHeaderView.Stretch)
self.tree.setColumnWidth(0, 400)
def on_item_expanded(self, item):
if is_lazy_item(item):
item.removeChild(item.child(0))
var = item.data(0, Qt.UserRole + 100)
if var:
for child_var in var.get('children', []):
self.add_tree_item_lazy(item, child_var)
def get_full_item_name(self, item):
fullname = item.text(0)
# Заменяем '->' на '.'
fullname = fullname.replace('->', '.')
fullname = fullname.replace('[', '.[')
return fullname
def add_tree_item_lazy(self, parent, var):
name = var['name']
type_str = var.get('type', '')
item = QTreeWidgetItem([name, type_str])
item.setData(0, Qt.UserRole, name)
full_name = self.get_full_item_name(item)
self.node_index[full_name.lower()] = item
if "(bitfield:" in type_str:
item.setDisabled(True)
self.set_tool(item, "Битовые поля недоступны для выбора")
for i, attr in enumerate(['file', 'extern', 'static']):
item.setData(0, Qt.UserRole + 1 + i, var.get(attr))
if parent is None:
self.tree.addTopLevelItem(item)
else:
parent.addChild(item)
# Если есть дети — добавляем заглушку (чтобы можно было раскрыть)
if var.get('children'):
dummy = QTreeWidgetItem(["lazy_marker"])
item.addChild(dummy)
# Кэшируем детей для подгрузки по событию
item.setData(0, Qt.UserRole + 100, var) # Сохраняем var целиком
def show_matching_path(self, item, path_parts, level=0):
node_name = item.text(0).lower()
node_parts = split_path(node_name)
if 'project' in node_name:
a = 1
if level >= len(path_parts):
# Путь полностью пройден — показываем только этот узел (без раскрытия всех детей)
item.setHidden(False)
item.setExpanded(False)
return True
if level >= len(node_parts):
# Уровень поиска больше длины пути узла — скрываем
item.setHidden(False)
search_part = path_parts[level]
node_part = node_parts[level]
if search_part == node_part:
# Точное совпадение — показываем узел, идём вглубь только по совпадениям
item.setHidden(False)
matched_any = False
self.on_item_expanded(item)
for i in range(item.childCount()):
child = item.child(i)
if self.show_matching_path(child, path_parts, level + 1):
matched_any = True
item.setExpanded(matched_any)
return matched_any or item.childCount() == 0
elif node_part.startswith(search_part):
# Неполное совпадение — показываем только этот узел, детей скрываем, не раскрываем
item.setHidden(False)
item.setExpanded(False)
return True
elif search_part in node_part and (level == len(path_parts)-1):
# Неполное совпадение — показываем только этот узел, детей скрываем, не раскрываем
item.setHidden(False)
item.setExpanded(False)
return True
else:
# Несовпадение — скрываем
item.setHidden(True)
return False
def filter_tree(self):
text = self.search_input.text().strip().lower()
path_parts = split_path(text) if text else []
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:
text = self.search_input.text().strip()
else:
text = text.strip()
normalized_text = text.replace('->', '.')
parts = split_path(text)
path_parts = parts[:-1] if parts else []
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.setCursorPosition(len(text))
def eventFilter(self, obj, event):
if obj == self.search_input and isinstance(event, QKeyEvent):
if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier:
self.manual_completion_active = True
text = self.search_input.text().strip()
self.run_completions(text)
elif event.key() == Qt.Key_Escape:
# Esc — выключаем ручной режим и скрываем подсказки, если autocomplete выключен
if not self.is_autocomplete_on:
self.manual_completion_active = False
self.completer.popup().hide()
return True
if event.key() == Qt.Key_Backspace:
self._bckspc_pressed = True
else:
self._bckspc_pressed = False
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:
text = text[:-1]
if len(completions) == 1 and completions[0].lower() == text.lower():
# Найдем узел с таким именем
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])
if node and node.childCount() > 0:
# Используем первую подсказку, чтобы определить нужный разделитель
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()
if text == None:
text = self.search_input.text().strip()
if self.is_autocomplete_on:
self.run_completions(text)
else:
# Если выключено, показываем подсказки только если флаг ручного вызова True
if self.manual_completion_active:
self.run_completions(text)
else:
self.completer.popup().hide()
def focusInEvent(self, event):
if self.completer.widget() != self.search_input:
self.completer.setWidget(self.search_input)
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):
self.completer.setWidget(None)
self.completer.deleteLater()
super().closeEvent(event)

392
Src/var_selector_window.py Normal file
View File

@@ -0,0 +1,392 @@
import re
import lxml.etree as ET
from PySide2.QtWidgets import (
QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton,
QLineEdit, QLabel, QHeaderView, QCompleter, QCheckBox, QHBoxLayout, QSizePolicy
)
from PySide2.QtGui import QKeySequence, QKeyEvent
from PySide2.QtCore import Qt, QStringListModel, QSettings
import var_table
import var_setup
import myXML
import time
import var_selector_table
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
class VariableSelectorDialog(QDialog):
def __init__(self, table, all_vars, structs, typedefs, xml_path=None, parent=None):
super().__init__(parent)
self.setWindowTitle("Выбор переменных")
self.setAttribute(Qt.WA_DeleteOnClose)
self.resize(1200, 500)
self.selected_names = []
self._bckspc_pressed = False # флаг подавления добавления разделителя
self.table = table
self.all_vars = all_vars
self.structs = structs
self.typedefs = typedefs
self.expanded_vars = []
self.var_map = {v['name']: v for v in all_vars}
self.node_index = {}
self.xml_path = xml_path # сохраняем путь к xml
self.manual_completion_active = False
# --- Добавляем чекбокс для автодополнения ---
self.autocomplete_checkbox = QCheckBox("Включить автодополнение")
self.autocomplete_checkbox.setChecked(True)
# Инициализируем QSettings с именем организации и приложения
self.settings = QSettings("SET", "DebugVarEdit_VarsSelector")
# Восстанавливаем сохранённое состояние чекбокса, если есть
checked = self.settings.value("autocomplete_enabled", True, type=bool)
self.autocomplete_checkbox.setChecked(checked)
# При изменении состояния чекбокса сохраняем его
self.autocomplete_checkbox.stateChanged.connect(self.save_checkbox_state)
# Кнопки между таблицами
self.btn_right = QPushButton(">")
self.btn_right.clicked.connect(self.on_move_right)
self.btn_left = QPushButton("<")
self.btn_left.clicked.connect(self.on_move_left)
# Создаем кнопки, они остаются в диалоге
self.btn_accept = QPushButton("Применить")
# Создаем экземпляр вашего готового виджета
self.vars_widget = var_selector_table.VariableSelectWidget(self)
self.vars_widget.tree.itemDoubleClicked.connect(self.on_left_tree_double_click)
self.vars_widget.setObjectName("LeftTable")
self.selected_vars_widget = var_selector_table.VariableSelectWidget(self)
self.selected_vars_widget.tree.itemDoubleClicked.connect(self.on_rigth_tree_double_click)
self.selected_vars_widget.setObjectName("RightTable")
# Подписи над таблицами
label_all = QLabel("Все переменные")
label_all.setStyleSheet("font-weight: bold; font-size: 14px;")
label_selected = QLabel("Выбранные переменные")
label_selected.setStyleSheet("font-weight: bold; font-size: 14px;")
# --- Лэйауты ---
main_layout = QVBoxLayout(self) # главный вертикальный layout окна
# Чекбокс автодополнения — первый в главном layout
main_layout.addWidget(self.autocomplete_checkbox)
# Подписи над таблицами
labels_layout = QHBoxLayout()
labels_layout.addWidget(label_all)
labels_layout.addStretch()
labels_layout.addWidget(label_selected)
main_layout.addLayout(labels_layout)
# Горизонтальный layout с таблицами и кнопками
tables_layout = QHBoxLayout()
# Левая таблица
self.vars_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
tables_layout.addWidget(self.vars_widget)
# Кнопки ">" и "<" между таблицами
middle_buttons_layout = QVBoxLayout()
middle_buttons_layout.addStretch()
middle_buttons_layout.addWidget(self.btn_right)
middle_buttons_layout.addWidget(self.btn_left)
middle_buttons_layout.addStretch()
tables_layout.addLayout(middle_buttons_layout)
# Правая таблица
self.selected_vars_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
tables_layout.addWidget(self.selected_vars_widget)
# Добавляем горизонтальный layout с таблицами в главный вертикальный
main_layout.addLayout(tables_layout)
# Кнопки "Добавить выбранные" и "Удалить выбранные" под таблицами
buttons_layout = QVBoxLayout()
buttons_layout.addWidget(self.btn_accept)
main_layout.addLayout(buttons_layout)
# Важно, если окно — QDialog или QWidget, установи layout
self.setLayout(main_layout)
# Соединяем сигналы кнопок с методами диалога
self.btn_accept.clicked.connect(self.on_apply_clicked)
# Соединяем чекбокс с методом виджета
self.autocomplete_checkbox.stateChanged.connect(self.set_autocomplete_tables)
# Устанавливаем начальное состояние автодополнения в виджете
self.vars_widget.set_autocomplete(self.autocomplete_checkbox.isChecked())
self.selected_vars_widget.set_autocomplete(self.autocomplete_checkbox.isChecked())
# --- Код в конце __init__ ---
self.expanded_vars = var_setup.expand_vars(self.all_vars, self.structs, self.typedefs)
self.update_vars_widget()
def on_move_right(self):
# Устанавливаем show_var=True для всех выбранных переменных из ЛЕВОЙ таблицы
selected = self.vars_widget._get_internal_selected_var_names()
if not selected:
return
def mark_selected_show_var(data):
for var in data:
if var['name'] in selected:
var['show_var'] = 'true'
var['enable'] = 'true'
if 'children' in var:
mark_selected_show_var(var['children'])
mark_selected_show_var(self.expanded_vars)
self.update_vars_widget()
def on_move_left(self):
# Сбрасываем show_var=False для всех выбранных переменных из ПРАВОЙ таблицы
selected = self.selected_vars_widget._get_internal_selected_var_names()
if not selected:
return
def mark_selected_hide_var(data):
for var in data:
if var['name'] in selected:
var['show_var'] = 'false'
if 'children' in var:
mark_selected_hide_var(var['children'])
mark_selected_hide_var(self.expanded_vars)
self.update_vars_widget()
def update_vars_widget(self):
t_start = time.perf_counter()
t1 = time.perf_counter()
self.selected_vars, self.unselected_vars = var_setup.split_vars_by_show_flag(self.expanded_vars)
t2 = time.perf_counter()
self.vars_widget.set_data(self.unselected_vars)
t3 = time.perf_counter()
self.vars_widget.filter_tree()
t4 = time.perf_counter()
self.selected_vars_widget.set_data(self.selected_vars)
t5 = time.perf_counter()
self.selected_vars_widget.filter_tree()
def on_apply_clicked(self):
# Получаем имена всех переменных из правой таблицы (selected_vars_widget)
right_var_names = set(self.selected_vars_widget.get_all_var_names())
all_items = self.selected_vars_widget.get_all_items()
if not all_items:
return
# Устанавливаем show_var=true и enable=true для переменных из правой таблицы
def add_or_update_var(item):
name = item.text(0)
type_str = item.text(1)
if name in self.var_map:
var = self.var_map[name]
var['show_var'] = 'true'
var['enable'] = 'true'
else:
file_val = item.data(0, Qt.UserRole + 1)
extern_val = item.data(0, Qt.UserRole + 2)
static_val = item.data(0, Qt.UserRole + 3)
new_var = {
'name': name,
'type': type_str,
'show_var': 'true',
'enable': 'true',
'shortname': name,
'pt_type': '',
'iq_type': '',
'return_type': 't_iq_none',
'file': file_val,
'extern': str(extern_val).lower() if extern_val else 'false',
'static': str(static_val).lower() if static_val else 'false',
}
self.all_vars.append(new_var)
self.var_map[name] = new_var
for item in all_items:
add_or_update_var(item)
# Сбрасываем show_var и enable у всех переменных, которых нет в правой таблице
for var in self.all_vars:
if var['name'] not in right_var_names:
var['show_var'] = 'false'
var['enable'] = 'false'
# Обновляем expanded_vars чтобы отразить новые show_var и enable
def update_expanded_vars(data):
for v in data:
name = v['name']
if name in self.var_map:
v['show_var'] = self.var_map[name]['show_var']
v['enable'] = self.var_map[name]['enable']
if 'children' in v:
update_expanded_vars(v['children'])
update_expanded_vars(self.expanded_vars)
# Обновляем отображение в виджетах
self.update_vars_widget()
# Закрываем диалог
self.accept()
# Обнови on_left_tree_double_click:
def on_left_tree_double_click(self, item, column):
selected_names = [item.text(0)]
if not selected_names:
return
def mark_selected_show_var(data):
for var in data:
if var['name'] in selected_names:
var['show_var'] = 'true'
var['enable'] = 'true'
if 'children' in var:
mark_selected_show_var(var['children'])
mark_selected_show_var(self.expanded_vars)
self.update_vars_widget()
# Добавь обработчик двойного клика справа (если нужно):
def on_rigth_tree_double_click(self, item, column):
selected_names = [item.text(0)]
if not selected_names:
return
def mark_selected_hide_var(data):
for var in data:
if var['name'] in selected_names:
var['show_var'] = 'false'
if 'children' in var:
mark_selected_hide_var(var['children'])
mark_selected_hide_var(self.expanded_vars)
self.update_vars_widget()
def keyPressEvent(self, event):
if event.key() == Qt.Key_Delete:
self.delete_selected_vars()
else:
super().keyPressEvent(event)
def delete_selected_vars(self):
selected_names = self._get_selected_var_names()
if not selected_names:
print("nothing selected")
return
# Обновляем var_map и all_vars
for name in selected_names:
if name in self.var_map:
self.var_map[name]['show_var'] = 'false'
self.var_map[name]['enable'] = 'false'
for v in self.all_vars:
if v['name'] == name:
v['show_var'] = 'false'
v['enable'] = 'false'
break
# Проверка пути к XML
if not hasattr(self, 'xml_path') or not self.xml_path:
from PySide2.QtWidgets import QMessageBox
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
return
root, tree = myXML.safe_parse_xml(self.xml_path)
if root is None:
return
vars_section = root.find('variables')
if vars_section is None:
return
for var_elem in vars_section.findall('var'):
name = var_elem.attrib.get('name')
if name in selected_names:
def set_text(tag, value):
el = var_elem.find(tag)
if el is None:
el = ET.SubElement(var_elem, tag)
el.text = value
set_text('show_var', 'false')
set_text('enable', 'false')
myXML.fwrite(root, self.xml_path)
self.table.populate(self.all_vars, self.structs, None)
# Проверка пути к XML
if not hasattr(self, 'xml_path') or not self.xml_path:
from PySide2.QtWidgets import QMessageBox
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно удалить переменные.")
return
root, tree = myXML.safe_parse_xml(self.xml_path)
if root is None:
return
vars_section = root.find('variables')
if vars_section is None:
return
removed_any = False
for var_elem in list(vars_section.findall('var')):
name = var_elem.attrib.get('name')
if name in selected_names:
vars_section.remove(var_elem)
removed_any = True
self.var_map.pop(name, None)
# Удаляем из all_vars (глобально)
self.all_vars[:] = [v for v in self.all_vars if v['name'] not in selected_names]
# Удаляем из expanded_vars (тоже глобально)
def filter_out_selected(vars_list):
filtered = []
for v in vars_list:
if v['name'] not in selected_names:
# Рекурсивно фильтруем детей, если есть
if 'children' in v:
v = v.copy()
v['children'] = filter_out_selected(v['children'])
filtered.append(v)
return filtered
self.expanded_vars[:] = filter_out_selected(self.expanded_vars)
if removed_any:
myXML.fwrite(root, self.xml_path)
self.update_vars_widget()
def _get_selected_var_names(self):
focused = self.focusWidget()
if focused and focused is self.vars_widget.tree:
return self.vars_widget.get_selected_var_names()
elif focused and focused is self.selected_vars_widget.tree:
return self.selected_vars_widget.get_selected_var_names()
else:
return []
def save_checkbox_state(self):
self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked())
def set_autocomplete_tables(self, state):
self.vars_widget.set_autocomplete(state)
self.selected_vars_widget.set_autocomplete(state)

View File

@@ -1,11 +1,63 @@
import sys import sys
import os import os
import re import re
import xml.etree.ElementTree as ET import lxml.etree as ET
from generateVars import map_type_to_pt, get_iq_define, type_map from generate_debug_vars import map_type_to_pt, get_iq_define, type_map
from enum import IntEnum from enum import IntEnum
import scanVars import scan_vars
import myXML import myXML
import pickle
# Вспомогательные функции, которые теперь будут использоваться виджетом
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 make_absolute_path(path, base_path): def make_absolute_path(path, base_path):
@@ -86,7 +138,7 @@ def parse_vars(filename, typedef_map=None):
'shortname': var.findtext('shortname', name), 'shortname': var.findtext('shortname', name),
'pt_type': pt_type, 'pt_type': pt_type,
'iq_type': iq_type, 'iq_type': iq_type,
'return_type': var.findtext('return_type', ''), 'return_type': var.findtext('return_type', 't_iq_none'),
'type': var_type, 'type': var_type,
'file': var.findtext('file', ''), 'file': var.findtext('file', ''),
'extern': var.findtext('extern', 'false') == 'true', 'extern': var.findtext('extern', 'false') == 'true',
@@ -239,7 +291,7 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
return process_array(prefix, type_str, structs, typedefs, var_attrs, depth) return process_array(prefix, type_str, structs, typedefs, var_attrs, depth)
# Ищем структуру по имени типа # Ищем структуру по имени типа
base_type = scanVars.strip_ptr_and_array(type_str) base_type = scan_vars.strip_ptr_and_array(type_str)
fields = structs.get(base_type) fields = structs.get(base_type)
if not isinstance(fields, dict): if not isinstance(fields, dict):
# Не структура и не массив — просто возвращаем пустой список # Не структура и не массив — просто возвращаем пустой список
@@ -266,6 +318,12 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
else: else:
field_type_str = None field_type_str = None
if '*' in field_type_str:
full_name_prefix = full_name + '*'
else:
full_name_prefix = full_name
# Обработка, если поле — строка (тип или массив) # Обработка, если поле — строка (тип или массив)
if field_type_str: if field_type_str:
base_subtype, sub_dims = parse_array_dims(field_type_str) base_subtype, sub_dims = parse_array_dims(field_type_str)
@@ -311,7 +369,7 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
if isinstance(field_value, dict): if isinstance(field_value, dict):
# Это одиночная структура — раскрываем рекурсивно # Это одиночная структура — раскрываем рекурсивно
sub_items = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1) sub_items = expand_struct_recursively(full_name_prefix, field_value, structs, typedefs, var_attrs, depth + 1)
child = { child = {
'name': full_name, 'name': full_name,
'type': field_type_str, 'type': field_type_str,
@@ -353,7 +411,7 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
'extern': var_attrs.get('extern'), 'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'), 'static': var_attrs.get('static'),
} }
subchildren = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1) subchildren = expand_struct_recursively(full_name_prefix, field_value, structs, typedefs, var_attrs, depth + 1)
if subchildren: if subchildren:
child['children'] = subchildren child['children'] = subchildren
children.append(child) children.append(child)
@@ -399,3 +457,163 @@ def expand_vars(vars_list, structs, typedefs):
return expanded return expanded
def build_full_names(parts, full_name):
"""
Восстанавливает вложенные полные имена из списка частей,
ориентируясь на оригинальное полное имя (с '.', '->' и индексами).
Пример:
parts = ['arr', '[0]', '[1]', 'ptr', 'val']
full_name = 'arr[0][1].ptr->val'
[
'arr',
'arr[0]',
'arr[0][1]',
'arr[0][1].ptr',
'arr[0][1].ptr->val'
]
"""
names = []
acc = ''
idx = 0
for part in parts:
pos = full_name.find(part, idx)
if pos == -1:
acc += part
else:
acc = full_name[:pos + len(part)]
idx = pos + len(part)
names.append(acc)
return names
def find_var_by_name(tree, name):
for var in tree:
if var.get('name') == name:
return var
if 'children' in var:
found = find_var_by_name(var['children'], name)
if found:
return found
return None
def add_to_nested_tree(tree, var, path_parts, full_names=None, depth=0, source_tree=None):
if not path_parts:
return
if full_names is None:
full_names = build_full_names(path_parts, var['name'])
current_name = full_names[depth]
for child in tree:
if child.get('name') == current_name:
if depth == len(path_parts) - 1:
child.update(var)
return
if 'children' not in child:
child['children'] = []
add_to_nested_tree(child['children'], var, path_parts, full_names, depth + 1, source_tree)
return
# Ищем в source_tree (expanded_vars) родительский узел по current_name
parent_data = {}
if source_tree:
parent_var = find_var_by_name(source_tree, current_name)
if parent_var:
# Копируем все поля кроме детей (children)
parent_data = {k: v for k, v in parent_var.items() if k != 'children'}
new_node = {
'name': current_name,
'children': []
}
# Обновляем new_node данными родителя
new_node.update(parent_data)
if depth == len(path_parts) - 1:
new_node.update(var)
else:
add_to_nested_tree(new_node['children'], var, path_parts, full_names, depth + 1, source_tree)
tree.append(new_node)
def split_vars_by_show_flag(expanded_vars):
unselected_vars = pickle.loads(pickle.dumps(expanded_vars, protocol=pickle.HIGHEST_PROTOCOL))
selected_vars = []
def find_and_remove(var_list, target_name):
"""Удаляет элемент по полному имени и возвращает его"""
for i, var in enumerate(var_list):
if var.get("name") == target_name:
return var_list.pop(i)
if 'children' in var:
found = find_and_remove(var['children'], target_name)
if found:
return found
return None
def collect_selected_nodes(var):
"""Рекурсивно возвращает все show_var=true узлы (включая поддерево)"""
nodes = []
if var.get('show_var', 'false').lower() == 'true':
nodes.append(var)
for child in var.get('children', []):
nodes.extend(collect_selected_nodes(child))
return nodes
def exists_by_path(tree, full_name):
"""
Проверяет, существует ли переменная в дереве, следуя по частям пути (например: project adc status).
Каждая часть ('project', 'project.adc', ...) должна иметь точное совпадение с 'name' в узле.
"""
path_parts = split_path(full_name)
full_names = build_full_names(path_parts, full_name)
current_level = tree
for name in full_names:
found = False
for var in current_level:
if var.get('name') == name:
current_level = var.get('children', [])
found = True
break
if not found:
return False
return True
selected_nodes = []
for var in expanded_vars:
full_name = var['name']
# Проверка: если имя содержит вложенность, но целиком есть в корне — пропускаем
if ('.' in full_name or '[' in full_name or '->' in full_name):
path_parts = split_path(full_name)
if exists_by_path(expanded_vars, full_name):
# Удалим лишнюю копию из корня unselected_vars
find_and_remove(unselected_vars, full_name)
else:
add_to_nested_tree(unselected_vars, var, path_parts, source_tree=expanded_vars)
find_and_remove(unselected_vars, full_name)
selected_nodes.extend(collect_selected_nodes(var))
for node in selected_nodes:
full_name = node['name']
path_parts = split_path(full_name)
# Вырезать из unselected_vars
removed = find_and_remove(unselected_vars, full_name)
if removed:
add_to_nested_tree(selected_vars, removed, path_parts, source_tree=expanded_vars)
else:
# вдруг удалённый родитель — создаём вручную
add_to_nested_tree(selected_vars, node, path_parts, source_tree=expanded_vars)
return selected_vars, unselected_vars

461
Src/var_table.py Normal file
View File

@@ -0,0 +1,461 @@
from PySide2.QtWidgets import (
QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter,
QAbstractItemView, QHeaderView, QLabel, QSpacerItem, QSizePolicy, QSpinBox,
QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QScrollArea, QWidget
)
from PySide2.QtGui import QColor, QBrush, QPalette
from PySide2.QtCore import Qt, QSettings
from enum import IntEnum
from generate_debug_vars import type_map
import time
from typing import Dict, List
class rows(IntEnum):
No = 0
include = 1
name = 2
type = 3
pt_type = 4
iq_type = 5
ret_type = 6
short_name = 7
class FilterDialog(QDialog):
def __init__(self, parent, options, selected, title="Выберите значения"):
super().__init__(parent)
self.setWindowTitle(title)
self.resize(250, 300)
self.selected = set(selected)
layout = QVBoxLayout(self)
scroll = QScrollArea(self)
scroll.setWidgetResizable(True)
container = QWidget()
scroll.setWidget(container)
self.checkboxes = []
vbox = QVBoxLayout(container)
for opt in options:
cb = QCheckBox(opt)
cb.setChecked(opt in self.selected)
vbox.addWidget(cb)
self.checkboxes.append(cb)
layout.addWidget(scroll)
btn_layout = QHBoxLayout()
btn_ok = QPushButton("OK")
btn_cancel = QPushButton("Отмена")
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_cancel)
layout.addLayout(btn_layout)
btn_ok.clicked.connect(self.accept)
btn_cancel.clicked.connect(self.reject)
def get_selected(self):
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):
def wheelEvent(self, event):
if event.modifiers() & Qt.ControlModifier:
super().wheelEvent(event)
else:
event.ignore()
class VariableTableWidget(QTableWidget):
def __init__(self, parent=None):
super().__init__(0, 8, parent)
# Таблица переменных
self.setHorizontalHeaderLabels([
'', # новый столбец
'En',
'Name',
'Origin Type',
'Base Type',
'IQ Type',
'Return Type',
'Short Name'
])
self.setEditTriggers(QAbstractItemView.AllEditTriggers)
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.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)]
# Задаём базовые iq-типы (без префикса 'iq_')
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 and
'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._iq_type_filter = list(self.iq_types) # Текущий фильтр iq типов (по умолчанию все)
self._pt_type_filter = list(self.pt_types)
self._ret_type_filter = list(self.iq_types)
header = self.horizontalHeader()
# Для остальных колонок — растяжение (Stretch), чтобы они заняли всю оставшуюся ширину
for col in range(self.columnCount()):
if col == self.columnCount() - 1:
header.setSectionResizeMode(col, QHeaderView.Stretch)
else:
header.setSectionResizeMode(col, QHeaderView.Interactive)
parent_widget = self.parentWidget()
# Сделаем колонки с номерами фиксированной ширины
self.setColumnWidth(rows.No, 30)
self.setColumnWidth(rows.include, 30)
self.setColumnWidth(rows.pt_type, 85)
self.setColumnWidth(rows.iq_type, 85)
self.setColumnWidth(rows.ret_type, 85)
self.setColumnWidth(rows.name, 300)
self.setColumnWidth(rows.type, 100)
self._resizing = False
self.horizontalHeader().sectionResized.connect(self.on_section_resized)
self.horizontalHeader().sectionClicked.connect(self.on_header_clicked)
def populate(self, vars_list, structs, on_change_callback):
self.var_list = vars_list
# --- ДО: удаляем отображение структур и union-переменных
for var in vars_list:
pt_type = var.get('pt_type', '')
if 'struct' in pt_type or 'union' in pt_type:
var['show_var'] = 'false'
var['enable'] = 'false'
filtered_vars = [v for v in vars_list if v.get('show_var', 'false') == 'true']
self.setRowCount(len(filtered_vars))
self.verticalHeader().setVisible(False)
style_with_padding = "padding-left: 5px; padding-right: 5px; font-size: 14pt; font-family: 'Segoe UI';"
for row, var in enumerate(filtered_vars):
# №
no_item = QTableWidgetItem(str(row))
no_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
self.setItem(row, rows.No, no_item)
# Enable
cb = QCheckBox()
cb.setChecked(var.get('enable', 'false') == 'true')
cb.stateChanged.connect(on_change_callback)
cb.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.include, cb)
# 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.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.name, name_edit)
# Origin Type (readonly)
origin_item = QTableWidgetItem(var.get('type', ''))
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
origin_item.setToolTip(var.get('type', '')) # Всплывающая подсказка
origin_item.setForeground(QBrush(Qt.black))
self.setItem(row, rows.type, origin_item)
# pt_type
pt_combo = CtrlScrollComboBox()
pt_combo.addItems(self.pt_types)
value = var['pt_type'].replace('pt_', '')
if value not in self.pt_types:
pt_combo.addItem(value)
pt_combo.setCurrentText(value)
pt_combo.currentTextChanged.connect(on_change_callback)
pt_combo.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.pt_type, pt_combo)
# iq_type
iq_combo = CtrlScrollComboBox()
iq_combo.addItems(self.iq_types)
value = var['iq_type'].replace('t_', '')
if value not in self.iq_types:
iq_combo.addItem(value)
iq_combo.setCurrentText(value)
iq_combo.currentTextChanged.connect(on_change_callback)
iq_combo.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.iq_type, iq_combo)
# return_type
ret_combo = CtrlScrollComboBox()
ret_combo.addItems(self.iq_types)
value = var['return_type'].replace('t_', '')
ret_combo.setCurrentText(value)
ret_combo.currentTextChanged.connect(on_change_callback)
ret_combo.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.ret_type, ret_combo)
# short_name
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.check()
def check(self):
warning_color = QColor("#FFFACD")
error_color = QColor("#FFB6C1")
tooltip_shortname = "Short Name длиннее 10 символов — будет обрезано при генерации"
tooltip_missing = 'Имя переменной не найдено среди переменных. Добавьте её через кнопку "Добавить переменные"'
var_names_set = {v.get('name') for v in self.var_list if v.get('name')}
t0 = time.time()
self.setUpdatesEnabled(False)
for row in range(self.rowCount()):
t1 = time.time()
name_widget = self.cellWidget(row, rows.name)
t2 = time.time()
name = name_widget.text() if name_widget else ""
short_name_edit = self.cellWidget(row, rows.short_name)
t3 = time.time()
shortname = short_name_edit.text() if short_name_edit else ""
long_shortname = len(shortname) > self._shortname_size
found = name in var_names_set
color = None
tooltip = ""
if not found:
color = error_color
tooltip = tooltip_missing
elif long_shortname:
color = warning_color
tooltip = tooltip_shortname
self.highlight_row(row, color, tooltip)
t4 = time.time()
self.setUpdatesEnabled(True)
#print(f"Row {row}: cellWidget(name) {t2-t1:.4f}s, cellWidget(shortname) {t3-t2:.4f}s, highlight_row {t4-t3:.4f}s")
def read_data(self):
result = []
for row in range(self.rowCount()):
cb = self.cellWidget(row, rows.include)
name = self.cellWidget(row, rows.name).text()
pt = self.cellWidget(row, rows.pt_type).currentText()
iq = self.cellWidget(row, rows.iq_type).currentText()
ret = self.cellWidget(row, rows.ret_type).currentText()
shortname = self.cellWidget(row, rows.short_name).text()
origin_type = self.item(row, rows.type).text()
result.append({
'show_var': True,
'enable': cb.isChecked(),
'name': name,
'pt_type': f'pt_{pt}',
'iq_type': f't_{iq}',
'return_type': f't_{ret}',
'shortname': shortname,
'type': origin_type,
})
return result
def on_header_clicked(self, logicalIndex):
if logicalIndex == rows.pt_type:
dlg = FilterDialog(self, self.pt_types_all, self._pt_type_filter, "Выберите базовые типы")
if dlg.exec_():
self._pt_type_filter = dlg.get_selected()
self.update_comboboxes({rows.pt_type: self._pt_type_filter})
elif logicalIndex == rows.iq_type:
dlg = FilterDialog(self, self.iq_types_all, self._iq_type_filter, "Выберите IQ типы")
if dlg.exec_():
self._iq_type_filter = dlg.get_selected()
self.update_comboboxes({rows.iq_type: self._iq_type_filter})
elif logicalIndex == rows.ret_type:
dlg = FilterDialog(self, self.iq_types_all, self._ret_type_filter, "Выберите IQ типы")
if dlg.exec_():
self._ret_type_filter = dlg.get_selected()
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()
def update_comboboxes(self, columns_filters: Dict[int, List[str]]):
"""
Обновляет combobox-ячейки в указанных столбцах таблицы.
:param columns_filters: dict, где ключ — индекс столбца,
значение — список допустимых вариантов для combobox.
"""
for row in range(self.rowCount()):
for col, allowed_items in columns_filters.items():
combo = self.cellWidget(row, col)
if combo:
current = combo.currentText()
combo.blockSignals(True)
combo.clear()
combo.addItems(allowed_items)
if current in allowed_items:
combo.setCurrentText(current)
else:
combo.setCurrentIndex(0)
combo.blockSignals(False)
def on_section_resized(self, logicalIndex, oldSize, newSize):
if self._resizing:
return # предотвращаем рекурсию
min_width = 50
delta = newSize - oldSize
right_index = logicalIndex + 1
if right_index >= self.columnCount():
# Если правая колока - нет соседа, ограничиваем минимальную ширину
if newSize < min_width:
self._resizing = True
self.setColumnWidth(logicalIndex, min_width)
self._resizing = False
return
self._resizing = True
try:
right_width = self.columnWidth(right_index)
new_right_width = right_width - delta
# Если соседняя колонка станет уже минимальной - подкорректируем левую
if new_right_width < min_width:
new_right_width = min_width
newSize = oldSize + (right_width - min_width)
self.setColumnWidth(logicalIndex, newSize)
self.setColumnWidth(right_index, new_right_width)
finally:
self._resizing = False
def highlight_row(self, row: int, color: QColor = None, tooltip: str = ""):
"""
Подсвечивает строку таблицы цветом `color`, не меняя шрифт.
Работает с QLineEdit, QComboBox, QCheckBox (включая обёртки).
Если `color=None`, сбрасывает подсветку.
"""
css_reset = "background-color: none; font: inherit;"
css_color = f"background-color: {color.name()};" if color else css_reset
for col in range(self.columnCount()):
item = self.item(row, col)
widget = self.cellWidget(row, col)
if item is not None:
current_bg = item.background().color() if item.background() else None
if color and current_bg != color:
item.setBackground(QBrush(color))
item.setToolTip(tooltip)
elif not color and current_bg is not None:
item.setBackground(QBrush(Qt.NoBrush))
item.setToolTip("")
elif widget is not None:
if widget.styleSheet() != css_color:
widget.setStyleSheet(css_color)
current_tip = widget.toolTip()
if color and current_tip != tooltip:
widget.setToolTip(tooltip)
elif not color and current_tip:
widget.setToolTip("")
def get_selected_var_names(self):
selected_indexes = self.selectedIndexes()
selected_rows = set(index.row() for index in selected_indexes)
names = []
for row in selected_rows:
name_widget = self.cellWidget(row, rows.name)
if name_widget:
name = name_widget.text()
if name:
names.append(name)
return names

View File

@@ -1,117 +1,148 @@
#include "debug_tools.h" #include "debug_tools.h"
#include "IQmathLib.h"
static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var); #if !defined(GLOBAL_Q)
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var); #define GLOBAL_Q 16
#endif
DebugLowLevel_t debug_ll = DEBUG_LOWLEVEL_INIT; ///< Ñòðóêòóðà îòëàäêè íèæíåãî óðîâíÿ (èíèöèàëèçàöèÿ)
long var_numb = 1; static int getDebugVar(DebugVar_t *var, int32_t *int_var, float *float_var);
long return_var; static int convertDebugVarToIQx(DebugVar_t *var, int32_t *ret_var);
long return_ll_var;
DebugVar_t dbg_var_ll; ///////////////////////////----EXAPLE-----//////////////////////////////
int result; int var_numb = 1; ///< Ïðèìåð ïåðåìåííîé äëÿ îòëàäêè
char ext_date[] = {7, 233, 11, 07, 16, 50}; DebugVarName_t var_name; ///< Èìÿ ïåðåìåííîé
int Debug_Test_Example(void) int32_t return_var; ///< Ïåðåìåííàÿ äëÿ âîçâðàòà ðåçóëüòàòà
int32_t return_ll_var; ///< Âîçâðàùàåìîå çíà÷åíèå ñ íèæíåãî óðîâíÿ
int result; ///< Ïåðåìåííàÿ ðåçóëüòàòà
DateTime_t ext_date = {2025, 11, 07, 16, 50}; ///< Ïðèìåð âíåøíåé äàòû ñáîðêè
/**
* @brief Ïðèìåð èñïîëüçîâàíèÿ ôóíêöèé îòëàäêè.
* @details Äåìîíñòðàöèîííàÿ ôóíêöèÿ äëÿ ðàáîòû ñ ïåðåìåííûìè îòëàäêè.
*/
void Debug_Test_Example(void)
{ {
result = Debug_ReadVar(&dbg_vars[var_numb], &return_var); result = Debug_ReadVar(var_numb, &return_var);
result = Debug_ReadVarName(var_numb, var_name);
if(Debug_LowLevel_Initialize(ext_date) == 0) if(Debug_LowLevel_Initialize(&ext_date) == 0)
result = Debug_LowLevel_ReadVar(&dbg_var_ll, &return_ll_var); result = Debug_LowLevel_ReadVar(&return_ll_var);
} }
///////////////////////////----PUBLIC-----//////////////////////////////
int Debug_LowLevel_ReadVar(DebugVar_t *var_ll, 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 (var_ll == NULL) if(return_32b == NULL)
return 1;
int32_t tmp_var;
if (var_ind >= DebugVar_Qnt)
return 1;
if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) ||
(dbg_vars[var_ind].ptr_type == pt_unknown))
return 1; return 1;
char *addr = var_ll->Ptr; return convertDebugVarToIQx(&dbg_vars[var_ind], return_32b);
unsigned long addr_val = (unsigned long)addr;
// Ðàçðåø¸ííûå äèàïàçîíû ïàìÿòè íà TMS320F2812
if (!(
(addr_val <= 0x0007FF) || // RAMM0 + RAMM1
(addr_val >= 0x008000 && addr_val <= 0x009FFF) || // L0 + L1 SARAM
(addr_val >= 0x3F8000 && addr_val <= 0x3F9FFF) || // PRAMH0 + DRAMH0
(addr_val >= 0x3D8000 && addr_val <= 0x3EFFFF) || // Flash A-F
(addr_val >= 0x3FF000 && addr_val <= 0x3FFFFF) // Boot ROM / Reset
)) {
return 2; // àäðåñ âíå äîïóñòèìîãî äèàïàçîíà, èãíîðèðóåì
}
convertDebugVarToIQx(var_ll, return_long);
return 0;
} }
/**
int Debug_ReadVar(DebugVar_t *var, long *return_long) * @brief ×èòàåò èìÿ ïåðåìåííîé ïî èíäåêñó.
* @param var_ind èíäåêñ ïåðåìåííîé.
* @param name_ptr óêàçàòåëü íà áóôåð èìåíè (DebugVarName_t).
* @return int 0: óñïåõ, 1: îøèáêà.
* @details Êîïèðóåò èìÿ ïåðåìåííîé â ïðåäîñòàâëåííûé áóôåð.
*/
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr)
{ {
long tmp_var; if(name_ptr == NULL)
if (var == NULL)
return 1;
if((var->ptr_type == pt_struct) || (var->ptr_type == pt_union) ||
(var->ptr_type == pt_unknown))
return 1; return 1;
convertDebugVarToIQx(var, return_long); if (var_ind >= DebugVar_Qnt)
return 0;
}
int Debug_ReadVarName(DebugVar_t *var, char *name_ptr)
{
if((var == NULL)||(name_ptr == NULL))
return 1; return 1;
int i; int i;
// Êîïèðîâàíèå ñ çàùèòîé îò ïåðåïîëíåíèÿ è ÿâíîé îñòàíîâêîé ïî '\0' // Êîïèðîâàíèå ñ çàùèòîé îò ïåðåïîëíåíèÿ è ÿâíîé îñòàíîâêîé ïî '\0'
for (i = 0; i < sizeof(var->name); i++) for (i = 0; i < sizeof(dbg_vars[var_ind].name); i++)
{ {
name_ptr[i] = var->name[i]; name_ptr[i] = dbg_vars[var_ind].name[i];
if (var->name[i] == '\0') if (dbg_vars[var_ind].name[i] == '\0')
break; break;
} }
// Ãàðàíòèðîâàííîå çàâåðøåíèå ñòðîêè (íà ñëó÷àé, åñëè â var->name íå áûëî '\0') // Ãàðàíòèðîâàííîå çàâåðøåíèå ñòðîêè (íà ñëó÷àé, åñëè â var->name íå áûëî '\0')
name_ptr[sizeof(var->name) - 1] = '\0'; name_ptr[sizeof(dbg_vars[var_ind].name) - 1] = '\0';
return 0; return 0;
} }
/**
* @brief ×èòàåò çíà÷åíèå ïåðåìåííîé îòëàäêè ñ íèæíåãî óðîâíÿ.
* @param return_32b óêàçàòåëü íà ïåðåìåííóþ, êóäà çàïèñûâàåòñÿ ðåçóëüòàò.
* @return int 0: óñïåõ, 1: îøèáêà, 2: íåäîïóñòèìûé àäðåñ.
* @details Èñïîëüçóåò àäðåññ, ïåðåäàâàåìûé ñ òåðìèíàëêè äëÿ ïîëó÷åíèÿ çíà÷åíèÿ.
*/
int Debug_LowLevel_ReadVar(int32_t *return_32b)
{
if (return_32b == NULL)
return 1;
if (debug_ll.isVerified == 0)
return 1;
int Debug_LowLevel_Initialize(const char* external_date) uint8_t *addr = debug_ll.dbg_var.Ptr;
uint32_t addr_val = (uint32_t)addr;
// Ðàçðåø¸ííûå äèàïàçîíû ïàìÿòè (èç .cmd ôàéëà)
if (!(
(addr_val <= 0x0007FF) || // RAMM0 + RAMM1
(addr_val >= 0x008120 && addr_val <= 0x009FFC) || // L0 + L1 SARAM
(addr_val >= 0x3F8000 && addr_val <= 0x3F9FFF) || // PRAMH0 + DRAMH0
(addr_val >= 0x3FF000 && addr_val <= 0x3FFFFF) || // BOOTROM + RESET
(addr_val >= 0x080002 && addr_val <= 0x09FFFF) || // RAMEX1
(addr_val >= 0x0F0000 && addr_val <= 0x0FFEFF) || // RAMEX4
(addr_val >= 0x100002 && addr_val <= 0x103FFF) || // RAMEX0 + RAMEX2 + RAMEX01
(addr_val >= 0x102000 && addr_val <= 0x103FFF) // RAMEX2
)) {
return 2; // Çàïðåù¸ííûé àäðåñ — íåëüçÿ ÷èòàòü
}
return convertDebugVarToIQx(&debug_ll.dbg_var, return_32b);
}
/**
* @brief Èíèöèàëèçàöèÿ îòëàäêè íà íèæíåì óðîâíå ïî äàòå ñáîðêè.
* @param external_date ñòðóêòóðà ñ äàòîé DateTime_t
* @return int 0: ñîâïàäàåò, 1: íå ñîâïàäàåò, -1: îøèáêà.
* @details Ñðàâíèâàåò äàòó êîìïèëÿöèè ñ çàïðàøèâàåìîé è èíèöèàëèçèðóåò îòëàäî÷íóþ ïåðåìåííóþ.
*/
int Debug_LowLevel_Initialize(DateTime_t* external_date)
{ {
if (external_date == NULL) { if (external_date == NULL) {
return -1; return -1;
} }
// Ïðåîáðàçóåì external_date â ñòðóêòóðó
DateTimeHex ext;
ext.year = (external_date[0] << 8) | external_date[1];
ext.day = external_date[2];
ext.month = external_date[3];
ext.hour = external_date[4];
ext.minute = external_date[5];
// Çàïîëíèì ñòðóêòóðó build èç ìàêðîñîâ (ïðåäïîëàãàåì, ÷òî îíè ÷èñëîâûå)
DateTimeHex build;
build.year = BUILD_YEAR; // íàïðèìåð 2025
build.month = BUILD_MONTH; // íàïðèìåð 7
build.day = BUILD_DATA; // íàïðèìåð 11
build.hour = BUILD_HOURS; // íàïðèìåð 16
build.minute = BUILD_MINUTES;// íàïðèìåð 50
// Ñðàâíåíèå âñåõ ïîëåé // Ñðàâíåíèå âñåõ ïîëåé
if (ext.year == build.year && if (external_date->year == debug_ll.build_date.year &&
ext.month == build.month && external_date->month == debug_ll.build_date.month &&
ext.day == build.day && external_date->day == debug_ll.build_date.day &&
ext.hour == build.hour && external_date->hour == debug_ll.build_date.hour &&
ext.minute == build.minute) external_date->minute == debug_ll.build_date.minute)
{ {
debug_ll.isVerified = 1;
return 0; // Ñîâïàëî return 0; // Ñîâïàëî
} }
debug_ll.isVerified = 0;
return 1; // Íå ñîâïàëî return 1; // Íå ñîâïàëî
} }
@@ -121,263 +152,136 @@ int Debug_LowLevel_Initialize(const char* external_date)
/////////////////////----INTERNAL FUNCTIONS-----//////////////////////// /////////////////////----INTERNAL FUNCTIONS-----////////////////////////
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var) /**
* @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)
{ {
long iq_numb, iq_united, iq_final; if (t == t_iq_none)
return 0; // áåç IQ, float, int
else if (t == t_iq)
return GLOBAL_Q; // îáùèé IQ, íàïðèìåð 24
else if (t >= t_iq1 && t <= t_iq30)
return (int)t - (int)t_iq1 + 1; // íàïðèìåð t_iq1 -> 1, t_iq2 -> 2 è ò.ä.
else
return -1; // îøèáêà
}
/**
* @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)
{
int32_t iq_numb, iq_united, iq_final;
float float_numb; float float_numb;
if(getDebugVar(var, &iq_numb, &float_numb) != 0) if(getDebugVar(var, &iq_numb, &float_numb) != 0)
return 1; return 1;
// ïðèâåäåíèå ê îäíîìó IQ int src_q = iqTypeToQ(var->iq_type);
switch(var->iq_type) int dst_q = iqTypeToQ(var->return_type);
{
case t_iq_none: if (src_q < 0 || dst_q < 0)
if(var->ptr_type == pt_float) return 2; // íåïðàâèëüíûé ôîðìàò
{
iq_united = _IQ(float_numb); int64_t iq_united64 = 0;
} int64_t iq_final64 = 0;
else
{ // Êîíâåðòàöèÿ ê GLOBAL_Q (64-áèò)
iq_united = _IQ(iq_numb); if (var->iq_type == t_iq_none) {
} if (var->ptr_type == pt_float) {
break; // float_numb óìíîæàåì íà 2^GLOBAL_Q
case t_iq1: // Ðåçóëüòàò ïðèâîäèì ê 64 áèòà
iq_united = _IQ1toIQ(iq_numb); iq_united64 = (int64_t)(float_numb * (1 << GLOBAL_Q));
break; } else {
case t_iq2: iq_united64 = ((int64_t)iq_numb) << GLOBAL_Q;
iq_united = _IQ2toIQ(iq_numb); }
break; } else {
case t_iq3: int shift = GLOBAL_Q - src_q;
iq_united = _IQ3toIQ(iq_numb); if (shift >= 0)
break; iq_united64 = ((int64_t)iq_numb) << shift;
case t_iq4: else
iq_united = _IQ4toIQ(iq_numb); iq_united64 = ((int64_t)iq_numb) >> (-shift);
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 ê çàïðàøèâàåìîìó // Êîíâåðòàöèÿ èç GLOBAL_Q â öåëåâîé IQ (64-áèò)
switch(var->return_type) if (var->return_type == t_iq_none) {
{ // Âîçâðàùàåì öåëîå, îòáðîñèâ äðîáíóþ ÷àñòü
case t_iq_none: *ret_var = (uint32_t)(iq_united64 >> GLOBAL_Q);
iq_final = (long)_IQtoF(iq_united); } else {
break; int shift = dst_q - GLOBAL_Q;
case t_iq1: if (shift >= 0)
iq_final = _IQtoIQ1(iq_united); iq_final64 = iq_united64 << shift;
break; else
case t_iq2: iq_final64 = iq_united64 >> (-shift);
iq_final = _IQtoIQ2(iq_united);
break; // Ïðîâåðÿåì ïåðåïîëíåíèå int32_t
case t_iq3: if (iq_final64 > 2147483647 || iq_final64 < -2147483648)
iq_final = _IQtoIQ3(iq_united); return 3; // ïåðåïîëíåíèå
break;
case t_iq4: *ret_var = (uint32_t)iq_final64;
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;
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) if (!var || !int_var || !float_var || !var->Ptr)
return 1; // îøèáêà: null óêàçàòåëü return 1; // îøèáêà: null óêàçàòåëü
char *addr = var->Ptr; uint8_t *addr = var->Ptr;
unsigned long addr_val = (unsigned long)addr; uint32_t addr_val = (uint32_t)addr;
switch (var->ptr_type) switch (var->ptr_type)
{ {
case pt_int8: // 8 áèò case pt_int8: // 8 áèò
if ((addr_val & ALIGN_8BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return 1; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile int8_t *)addr);
case pt_uint8: case pt_uint8:
// âûðàâíèâàíèå íå íóæíî äëÿ 8 áèò if ((addr_val & ALIGN_8BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
*int_var = *((volatile char *)addr); return 1; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile uint8_t *)addr);
break; break;
case pt_int16: // 16 áèò (int) case pt_int16: // 16 áèò (int)
case pt_uint16: if ((addr_val & ALIGN_16BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
if (addr_val & 0x1) // ïðîâåðêà âûðàâíèâàíèÿ ïî 2 áàéòàì
return 2; // îøèáêà âûðàâíèâàíèÿ return 2; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile int *)addr); *int_var = *((volatile int16_t *)addr);
case pt_uint16:
if ((addr_val & ALIGN_16BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return 2; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile uint16_t *)addr);
break; break;
case pt_int32: // 32 áèò (long) case pt_int32: // 32 áèò
case pt_uint32: if ((addr_val & ALIGN_32BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
if (addr_val & 0x3) // ïðîâåðêà âûðàâíèâàíèÿ ïî 4 áàéòàì
return 3; // îøèáêà âûðàâíèâàíèÿ return 3; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile long *)addr); *int_var = *((volatile int32_t *)addr);
case pt_uint32:
if ((addr_val & ALIGN_32BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return 3; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile uint32_t *)addr);
break; break;
// case pt_int64: // 64 áèò (long long)
// case pt_uint64:
// if (addr_val & 0x7) // ïðîâåðêà âûðàâíèâàíèÿ ïî 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 & 0x3) // ïðîâåðêà âûðàâíèâàíèÿ ïî 4 áàéòàì if ((addr_val & ALIGN_FLOAT) != 0) // ïðîâåðêà âûðàâíèâàíèÿ
return 4; // îøèáêà âûðàâíèâàíèÿ return 4; // îøèáêà âûðàâíèâàíèÿ
*float_var = *((volatile float *)addr); *float_var = *((volatile float *)addr);
break; break;
@@ -401,3 +305,4 @@ static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var)
return 0; // óñïåõ return 0; // óñïåõ
} }

View File

@@ -1,8 +1,40 @@
#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>
#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
/**
* @brief Òèï äàííûõ, íà êîòîðûé óêàçûâàåò óêàçàòåëü ïåðåìåííîé îòëàäêè.
*/
typedef enum typedef enum
{ {
pt_unknown, // unknown pt_unknown, // unknown
@@ -14,7 +46,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 +63,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,34 +102,67 @@ typedef enum
t_iq30 t_iq30
}DebugVarIQType_t; }DebugVarIQType_t;
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 âîçâðàùàåìîãî çíà÷åíèÿ
char name[11]; // 10 ñèìâîëîâ + '\0' 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)
} DateTimeHex; } DateTime_t;
/**
* @brief Ñòðóêòóðà íèæíåãî óðîâíÿ îòëàäêè.
*/
typedef struct
{
DateTime_t build_date; ///< Äàòà ñáîðêè
unsigned int isVerified; ///< Ôëàã èíèöèàëèçàöèè íèçêîóðîâíåíîé îòëàäêè (0 — íåò, 1 — óñïåøíî)
DebugVar_t dbg_var; ///< Ïåðåìåííàÿ äëÿ îòëàäêè
}DebugLowLevel_t;
extern DebugLowLevel_t debug_ll; ///< Ãëîáàëüíûé ýêçåìïëÿð îòëàäêè íèæíåãî óðîâíÿ
extern int DebugVar_Qnt; /** @brief Ìàêðîñ èíèöèàëèçàöèè äàòû */
extern DebugVar_t dbg_vars[]; #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"}
/** @brief Ìàêðîñ èíèöèàëèçàöèè íèæíåãî óðîâíÿ îòëàäêè */
#define DEBUG_LOWLEVEL_INIT {DATE_INIT, 0, DEBUG_VAR_INIT}
int Debug_Test_Example(void); extern int DebugVar_Qnt; ///< Êîëè÷åñòâî ïåðåìåííûõ îòëàäêè
extern DebugVar_t dbg_vars[]; ///< Ìàññèâ ïåðåìåííûõ îòëàäêè
int Debug_LowLevel_ReadVar(DebugVar_t *var_ll, long *return_long);
int Debug_ReadVar(DebugVar_t *var, long *return_long); /* Ïðèìåð èñïîëüçîâàíèÿ îòëàäêè */
int Debug_ReadVarName(DebugVar_t *var, char *name_ptr); void Debug_Test_Example(void);
int Debug_LowLevel_Initialize(const char* external_date);
/* ×èòàåò çíà÷åíèå ïåðåìåííîé ïî èíäåêñó */
int Debug_ReadVar(int var_ind, int32_t *return_long);
/* ×èòàåò èìÿ ïåðåìåííîé ïî èíäåêñó */
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr);
/* ×èòàåò çíà÷åíèå ïåðåìåííîé ñ íèæíåãî óðîâíÿ */
int Debug_LowLevel_ReadVar(int32_t *return_long);
/* Èíèöèàëèçèðóåò îòëàäêó íèæíåãî óðîâíÿ */
int Debug_LowLevel_Initialize(DateTime_t *external_date);
#endif //DEBUG_TOOLS #endif //DEBUG_TOOLS

View File

@@ -1,337 +0,0 @@
// Ýòîò ôàéë ñãåíåðèðîâàí àâòîìàòè÷åñêè
#include "debug_tools.h"
// Èíêëþäû äëÿ äîñòóïà ê ïåðåìåííûì
#include "vector.h"
#include "RS_Functions_modbus.h"
#include "f281xpwm.h"
#include "xp_project.h"
#include "adc_tools.h"
#include "errors.h"
#include "xp_write_xpwm_time.h"
#include "log_can.h"
#include "pwm_vector_regul.h"
#include "rotation_speed.h"
#include "teta_calc.h"
#include "v_pwm24.h"
#include "dq_to_alphabeta_cos.h"
#include "RS_Functions.h"
#include "x_serial_bus.h"
#include "x_parallel_bus.h"
#include "Spartan2E_Functions.h"
#include "xp_controller.h"
#include "xp_rotation_sensor.h"
#include "xPeriphSP6_loader.h"
#include "detect_phase_break2.h"
#include "CRC_Functions.h"
#include "CAN_Setup.h"
#include "log_params.h"
#include "log_to_memory.h"
#include "global_time.h"
#include "pid_reg3.h"
#include "svgen_dq.h"
#include "IQmathLib.h"
#include "doors_control.h"
#include "isolation.h"
#include "main22220.h"
#include "optical_bus.h"
#include "alarm_log_can.h"
#include "bender.h"
#include "can_watercool.h"
#include "detect_phase_break.h"
#include "modbus_read_table.h"
#include "rmp_cntl_my1.h"
// Ýêñòåðíû äëÿ äîñòóïà ê ïåðåìåííûì
extern int ADC0finishAddr;
extern int ADC0startAddr;
extern int ADC1finishAddr;
extern int ADC1startAddr;
extern int ADC2finishAddr;
extern int ADC2startAddr;
extern int ADC_f[2][16];
extern int ADC_sf[2][16];
extern int ADDR_FOR_ALL;
extern int BUSY;
extern BENDER Bender[2];
extern int CAN_answer_wait[32];
extern int CAN_count_cycle_input_units[8];
extern int CAN_no_answer[32];
extern int CAN_refresh_cicle[32];
extern int CAN_request_sent[32];
extern int CAN_timeout[32];
extern int CAN_timeout_cicle[32];
extern int CNTRL_ADDR;
extern const int CNTRL_ADDR_UNIVERSAL;
extern _iq CONST_15;
extern _iq CONST_23;
extern int CanOpenUnites[30];
extern int CanTimeOutErrorTR;
extern XControll_reg Controll;
extern int Dpwm;
extern int Dpwm2;
extern int Dpwm4;
extern int EvaTimer1InterruptCount;
extern int EvaTimer2InterruptCount;
extern int EvbTimer3InterruptCount;
extern int EvbTimer4InterruptCount;
extern int Fpwm;
extern int IN0finishAddr;
extern int IN0startAddr;
extern int IN1finishAddr;
extern int IN1startAddr;
extern int IN2finishAddr;
extern int IN2startAddr;
extern float IQ_OUT_NOM;
extern long I_OUT_1_6_NOMINAL_IQ;
extern long I_OUT_1_8_NOMINAL_IQ;
extern float I_OUT_NOMINAL;
extern long I_OUT_NOMINAL_IQ;
extern long I_ZPT_NOMINAL_IQ;
extern _iq Id_out_max_full;
extern _iq Id_out_max_low_speed;
extern _iq Iq_out_max;
extern _iq Iq_out_nom;
extern const unsigned long K_LEM_ADC[20];
extern float KmodTerm;
extern int ROTfinishAddr;
extern unsigned int RS_Len[70];
extern const unsigned int R_ADC[20];
extern int RotPlaneStartAddr;
extern _iq SQRT_32;
extern int Unites[8][128];
extern int VAR_FREQ_PWM_XTICS;
extern int VAR_PERIOD_MAX_XTICS;
extern int VAR_PERIOD_MIN_BR_XTICS;
extern int VAR_PERIOD_MIN_XTICS;
extern int Zpwm;
extern WINDING a;
extern volatile AddrToSent addrToSent;
extern unsigned int adr_read_from_modbus3;
extern ALARM_LOG_CAN alarm_log_can;
extern ALARM_LOG_CAN_SETUP alarm_log_can_setup;
extern ANALOG_VALUE analog;
extern int ar_sa_all[3][6][4][7];
extern _iq ar_tph[7];
extern int block_size_counter_fast;
extern int block_size_counter_slow;
extern _iq break_result_1;
extern _iq break_result_2;
extern _iq break_result_3;
extern _iq break_result_4;
extern Byte byte;
extern long c_s;
extern int calibration1;
extern int calibration2;
extern test_functions callfunc;
extern CANOPEN_CAN_SETUP canopen_can_setup;
extern unsigned int capnum0;
extern unsigned int capnum1;
extern unsigned int capnum2;
extern unsigned int capnum3;
extern unsigned int chNum;
extern BREAK_PHASE_I chanell1;
extern BREAK_PHASE_I chanell2;
extern int cmd_3_or_16;
extern int compress_size;
extern ControlReg controlReg;
extern COS_FI_STRUCT cos_fi;
extern unsigned int count_error_sync;
extern int count_modbus_table_changed;
extern int count_run_pch;
extern WORD crc_16_tab[256];
extern char crypt[34];
extern int cur_position_buf_modbus16_can;
extern CYCLE cycle[32];
extern int delta_capnum;
extern int delta_error;
extern volatile DOORS_STATUS doors;
extern int enable_can;
extern int enable_can_recive_after_units_box;
extern _iq err_level_adc;
extern _iq err_level_adc_on_go;
extern unsigned int err_main;
extern int err_modbus16;
extern int err_modbus3;
extern ERRORS errors;
extern FLAG f;
extern volatile int fail;
extern FAULTS faults;
extern FIFO fifo;
extern ANALOG_VALUE filter;
extern int flag_buf;
extern int flag_enable_can_from_mpu;
extern int flag_enable_can_from_terminal;
extern int flag_on_off_pch;
extern unsigned int flag_received_first_mess_from_MPU;
extern unsigned int flag_reverse;
extern unsigned int flag_send_answer_rs;
extern int flag_test_tabe_filled;
extern int flag_we_int_pwm_on;
extern _iq freq1;
extern float freqTerm;
extern GLOBAL_TIME global_time;
extern int hb_logs_data;
extern int i;
extern BREAK2_PHASE i1_out;
extern BREAK2_PHASE i2_out;
extern int init_log[3];
extern _iq19 iq19_k_norm_ADC[20];
extern _iq19 iq19_zero_ADC[20];
extern _iq iq_alfa_coef;
extern _iq iq_k_norm_ADC[20];
extern IQ_LOGSPARAMS iq_logpar;
extern _iq iq_max;
extern _iq iq_norm_ADC[20];
extern ISOLATION isolation1;
extern ISOLATION isolation2;
extern _iq k1;
extern float kI_D;
extern float kI_D_Inv31;
extern float kI_Q;
extern float kI_Q_Inv31;
extern float kP_D;
extern float kP_D_Inv31;
extern float kP_Q;
extern float kP_Q_Inv31;
extern _iq koef_Base_stop_run;
extern _iq koef_Iabc_filter;
extern _iq koef_Im_filter;
extern _iq koef_Im_filter_long;
extern _iq koef_K_stop_run;
extern _iq koef_Krecup;
extern _iq koef_Min_recup;
extern _iq koef_TemperBSU_long_filter;
extern _iq koef_Ud_fast_filter;
extern _iq koef_Ud_long_filter;
extern _iq koef_Wlong;
extern _iq koef_Wout_filter;
extern _iq koef_Wout_filter_long;
extern long koeff_Fs_filter;
extern long koeff_Idq_filter;
extern _iq koeff_Iq_filter;
extern long koeff_Iq_filter_slow;
extern long koeff_Ud_filter;
extern long koeff_Uq_filter;
extern volatile unsigned long length;
extern _iq level_on_off_break[13][2];
extern logcan_TypeDef log_can;
extern LOG_CAN_SETUP log_can_setup;
extern TYPE_LOG_PARAMS log_params;
extern long logbuf_sync1[10];
extern LOGSPARAMS logpar;
extern int m_PWM;
extern MAILBOXS_CAN_SETUP mailboxs_can_setup;
extern int manufactorerAndProductID;
extern MODBUS_REG_STRUCT * modbus_table_can_in;
extern MODBUS_REG_STRUCT * modbus_table_can_out;
extern MODBUS_REG_STRUCT modbus_table_in[450];
extern MODBUS_REG_STRUCT modbus_table_out[450];
extern MODBUS_REG_STRUCT * modbus_table_rs_in;
extern MODBUS_REG_STRUCT * modbus_table_rs_out;
extern MODBUS_REG_STRUCT modbus_table_test[450];
extern MPU_CAN_SETUP mpu_can_setup;
extern NEW_CYCLE_FIFO new_cycle_fifo;
extern int no_write;
extern int no_write_slow;
extern int number_modbus_table_changed;
extern OPTICAL_BUS_DATA optical_read_data;
extern OPTICAL_BUS_DATA optical_write_data;
extern MODBUS_REG_STRUCT options_controller[200];
extern _iq pidCur_Ki;
extern PIDREG3 pidD;
extern PIDREG3 pidD2;
extern PIDREG3 pidFvect;
extern int pidFvectKi_test;
extern int pidFvectKp_test;
extern PIDREG3 pidPvect;
extern PIDREG3 pidQ;
extern PIDREG3 pidQ2;
extern PIDREG_KOEFFICIENTS pidReg_koeffs;
extern PIDREG3 pidTetta;
extern POWER_RATIO power_ratio;
extern int prev_flag_buf;
extern unsigned int prev_status_received;
extern T_project project;
extern PWMGEND pwmd;
extern T_controller_read r_c_sbus;
extern T_controller_read r_controller;
extern FIFO refo;
extern TMS_TO_TERMINAL_STRUCT reply;
extern TMS_TO_TERMINAL_TEST_ALL_STRUCT reply_test_all;
extern RMP_MY1 rmp_freq;
extern RMP_MY1 rmp_wrot;
extern T_rotation_sensor rotation_sensor;
extern ROTOR_VALUE rotor;
extern RS_DATA_STRUCT rs_a;
extern RS_DATA_STRUCT rs_b;
extern unsigned int sincronisationFault;
extern char size_cmd15;
extern char size_cmd16;
extern int size_fast_done;
extern int size_slow_done;
extern int stop_log;
extern int stop_log_slow;
extern SVGENDQ svgen_dq_1;
extern SVGENDQ svgen_dq_2;
extern SVGEN_PWM24 svgen_pwm24_1;
extern SVGEN_PWM24 svgen_pwm24_2;
extern unsigned int temp;
extern _iq temperature_limit_koeff;
extern INVERTER_TEMPERATURES temperature_warning_BI1;
extern INVERTER_TEMPERATURES temperature_warning_BI2;
extern RECTIFIER_TEMPERATURES temperature_warning_BV1;
extern RECTIFIER_TEMPERATURES temperature_warning_BV2;
extern TERMINAL_CAN_SETUP terminal_can_setup;
extern TETTA_CALC tetta_calc;
extern int timCNT_alg;
extern int timCNT_prev;
extern unsigned int time;
extern float time_alg;
extern long time_pause_enable_can_from_mpu;
extern long time_pause_enable_can_from_terminal;
extern int time_pause_logs;
extern int time_pause_titles;
extern volatile int tryNumb;
extern UNITES_CAN_SETUP unites_can_setup;
extern VECTOR_CONTROL vect_control;
extern WaterCooler water_cooler;
extern _iq winding_displacement;
extern Word word;
extern WordReversed wordReversed;
extern WordToReverse wordToReverse;
extern X_PARALLEL_BUS x_parallel_bus_project;
extern X_SERIAL_BUS x_serial_bus_project;
extern unsigned int xeeprom_controll_fast;
extern unsigned int xeeprom_controll_store;
extern XPWM_TIME xpwm_time;
extern _iq zadan_Id_min;
extern int zero_ADC[20];
// Îïðåäåëåíèå ìàññèâà ñ óêàçàòåëÿìè íà ïåðåìåííûå äëÿ îòëàäêè
int DebugVar_Qnt = 19;
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
DebugVar_t dbg_vars[] = {\
{(char *)&ADC0finishAddr, pt_int16, t_iq_none, t_iq_none, "ADC0EndAdr" }, \
{(char *)&ADC_f[0][0], pt_int16, t_iq_none, t_iq_none, "ADC_f00" }, \
{(char *)&ADC_f[0][1], pt_int16, t_iq_none, t_iq_none, "ADC_f01" }, \
{(char *)&ADC_f[0][2], pt_int16, t_iq_none, t_iq_none, "ADC_f02" }, \
{(char *)&ADC_f[0][3], pt_int16, t_iq_none, t_iq_none, "ADC_f03" }, \
{(char *)&ADC_f[0][4], pt_int16, t_iq_none, t_iq_none, "ADC_f04" }, \
{(char *)&ADC_f[0][5], pt_int16, t_iq_none, t_iq_none, "ADC_f05" }, \
{(char *)&ADC_f[0][6], pt_int16, t_iq_none, t_iq_none, "ADC_f06" }, \
{(char *)&ADC_f[0][7], pt_int16, t_iq, t_iq_none, "ADC_f07" }, \
{(char *)&ADC_f[0][8], pt_int16, t_iq_none, t_iq_none, "ADC_f08" }, \
{(char *)&ADC_f[0][9], pt_int16, t_iq_none, t_iq_none, "ADC_f09" }, \
{(char *)&ADC_f[0][10], pt_int16, t_iq_none, t_iq_none, "ADC_f010" }, \
{(char *)&ADC_f[0][11], pt_int16, t_iq_none, t_iq_none, "ADC_f011" }, \
{(char *)&ADC_f[0][12], pt_int16, t_iq_none, t_iq_none, "ADC_f012" }, \
{(char *)&ADC_f[0][13], pt_int16, t_iq_none, t_iq_none, "ADC_f013" }, \
{(char *)&ADC_f[0][14], pt_int16, t_iq_none, t_iq_none, "ADC_f014" }, \
{(char *)&ADC_f[0][15], pt_int16, t_iq_none, t_iq_none, "ADC_f015" }, \
{(char *)&project.cds_tk[2].read.sbus.mask_protect_tk.all, pt_uint16, t_iq_none, t_iq_none, "tk2_ackcur" }, \
{(char *)&project.cds_tk[1].plane_address, pt_uint16, t_iq_none, t_iq_none, "tk1_adr" }, \
};

23
debug_vars_example.c Normal file
View File

@@ -0,0 +1,23 @@
#include "debug_tools.h"
// Инклюды для доступа к переменным
#include "bender.h"
// Экстерны для доступа к переменным
extern int ADC0finishAddr;
// Определение массива с указателями на переменные для отладки
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" }, \
};

20181
structs.xml

File diff suppressed because it is too large Load Diff

4872
vars.xml

File diff suppressed because it is too large Load Diff