29 Commits

Author SHA1 Message Date
Razvalyaev
910bf0a585 Обновлены readme 2025-07-23 18:18:19 +03:00
Razvalyaev
502046091c опять кууууча всего:
базово доделаны терминалки до более менее итогового состояния
2025-07-23 17:13:28 +03:00
Razvalyaev
e99de603e6 попытка сделать в parse_xml парсинг вложенных массивов [][]
определяет вложенные массивы но не определяет их размерности (нули)
2025-07-22 18:57:59 +03:00
Razvalyaev
788ad19464 кууууча всего по терминалке, надо резгребать и структурировать
базово:
+сделан lowlevel для кучи переменных (пока работает медленно)
+сделан сохранение принимаемых значений в лог
+ gui терминалок подогнаны под один стиль плюс минус
2025-07-22 18:05:12 +03:00
Razvalyaev
96496a0256 все неплохо работает.
сейв перед попыткой улучшить lowlevel debug
2025-07-21 13:40:52 +03:00
Razvalyaev
f89aff1b1c Улучшена скорость полла Watch (переделано формирование таблицы) 2025-07-19 18:17:00 +03:00
Razvalyaev
6830743477 Начата работа над lowlevel терминалкой (по адресам из xml) 2025-07-19 18:01:40 +03:00
Razvalyaev
171f176d63 добавлен exe для парса всех переменных из .out 2025-07-19 11:36:32 +03:00
Razvalyaev
f2c4b7b3cd структурирован код debug_tools
доработана демо-терминалка для считывания tms переменных и встроена в DebugVarEdit
2025-07-19 10:56:46 +03:00
Razvalyaev
c94a7e711c сделана бета терминалка для опроса переменных 2025-07-18 17:43:23 +03:00
Razvalyaev
5be6343c33 бета терминалка для опроса 2025-07-18 16:47:18 +03:00
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
32 changed files with 6334 additions and 26987 deletions

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@
/DebugVarEdit_GUI.build
/DebugVarEdit_GUI.dist
/DebugVarEdit_GUI.onefile-build
/parse_xml/build/
/parse_xml/Src/__pycache__/

Binary file not shown.

BIN
DebugVarTerminal.exe Normal file

Binary file not shown.

282
README.md Normal file
View File

@@ -0,0 +1,282 @@
# DebugTools - Просмотр переменных по указателям
## Содержание
1. [Описание модуля](#программный-модуль-debugtools)
2. [Описание приложения для настройки](#debugvaredit---настройка-переменных)
3. [Описание терминалки для считывания](#debugvarterminal---считывание-переменных-для-tms)
4. [Для разработчиков](#для-разработчиков)
# Программный модуль 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, int *length);
/* Читает возвращаемый тип (IQ) переменной по индексу */
int Debug_ReadVarReturnType(int var_ind, int *vartype);
/* Читает тип переменной по индексу */
int Debug_ReadVarType(int var_ind, int *vartype);
/* Читает значение переменной с нижнего уровня */
int Debug_LowLevel_ReadVar(int32_t *return_long);
/* Инициализирует отладку нижнего уровня */
int Debug_LowLevel_Initialize(DateTime_t *external_date);
/* Читает возвращаемый тип (IQ) низкоуровнено заданной переменной */
int Debug_LowLevel_ReadVarReturnType(int *vartype);
/* Читает тип низкоуровнено заданной переменной.*/
int Debug_LowLevel_ReadVarType(int *vartype);
```
Переменные доступные для чтения определяются в **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" }, \
};
```
## LowLevel - Просмотр абсолютно любых переменных
Также присутствует утилита `parse_xml.exe`, которая генерирует `.xml` файл с всеми переменными в программе и их адрессами
Для её подключения:
- В Pre-Build Steps добавить следующую команду. Это удаление `debug_tools.obj` файла для его перекомпиялции с актуальной датой компиляции
```
cmd /c del /Q "${CWD}\Src\DebugTools\debug_tools.obj"
```
- В Post-Build Steps добавить следующую команду. Это:
- формирование с помощью утилиты компилятора `ofd2000` `.xml` файла с полной отладочной информацией.
- формирование отладочной информации в текстовом файле (для получения таймштампа)
- запуск утилиты `parse_xml.exe` для формирования итогового `<projname>_allVars.xml` с информацией о переменных и адресах
```
"${CG_TOOL_ROOT}/bin/ofd2000" --obj_display=symbols --dwarf --dwarf_display=all --xml --xml_indent=1 --output="${BuildArtifactFileBaseName}_ofd_dump.xml" "${BuildArtifactFilePath}"
"${CG_TOOL_ROOT}/bin/ofd2000" "${BuildArtifactFilePath}" > ${CCS_PROJECT_DIR}/bin/temp.txt
"${CCS_PROJECT_DIR}/Src/DebugTools/parse_xml/parse_xml.exe" ${BuildArtifactFileBaseName}_ofd_dump.xml ${CCS_PROJECT_DIR}/bin/temp.txt ${BuildArtifactFileBaseName}_allVars.xml"
```
После, с использованием терминалки можно прочитать любые переменные по адресам. (должен совпадать таймштапм в прошивке и `.xml` файла, иначе контроллер ответит ошибкой)
# 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>
```
---
# DebugVarTerminal - Считывание переменных (для TMS)
**DebugVarTerminal** — терминалка для считывания переменных по RS-232 протоколу RS_Functions.
Программа — один исполняемый файл `DebugVarTerminal.exe`, не требующий установки и дополнительных зависимостей.
> Требуется Windows 7 или новее.
---
## Как использовать приложение
1. Запустите **DebugVarTerminal.exe.**
2. Выберите COM Port и скорость Baud. Нажмиите **Open**
3. Для считывания переменных заданных в `*debug_vars.c`:
- Откройте вкладку **Watch**.
- Выберите стартовый индекс переменной и количество переменных для считывания.
- Нажмите одну из кнопок:
- **Update Service** - считывает информацию об именах и возвращаемых IQ типах переемнных
- **Read Value(s)** - считывает и форматирует значения переменных в соответствии с IQ типами
- **Start Polling** - начать опрос выбранных переменных с заданным интервалом. При старте опроса, имя и тип переменных считываются автоматически
4. Для считывания переменных по адресам:
- Откройте вкладку **LowLevel**.
- Выберите `projname_allVars.xml` в папке bin рядом с бинарником. Из него подгрузятся все доступные для считывания переменные
- Выберите переменные кнопкной **Выбрать переменные**
- Задайте IQ тип и возвращаемый IQ тип если требуется
- Нажмите одну из кнопок:
- **Read Once** - считывает выбранные переменные один раз
- **Start Polling** - начать опрос выбранных переменных с заданным интервалом
5. Запись в CSV:
- Можно записывавать считываемые переменные в CSV файл, с разделителем `;`
- Нажмите кнопку **Начать запись в CSV**
- Когда нужная выборка будет накоплена нажмите **Остаовить запись в CSV**
- Нажмите **Выбрать файл CSV** для выбора пути для сохранения файла и нажмите **Сохранить данные в CSV** чтобы сохранить
- Генерируется совместимый с LogView `.csv` файл
Все параметры выбираются из выпадающих списков, которые можно настроить, чтобы отображались только нужные опции.
---
## Возможности
Режим "Watch":
- Быстрое и удобное чтение одной или нескольких переменных по их индексу.
- Автоматическое получение имен, типов и параметров масштабирования (IQ) переменных.
- Запуск постоянного опроса для отслеживания изменений значений в реальном времени.
- Наглядное представление данных в таблице с отображением как сырых, так и масштабированных значений.
Режим "LowLevel":
- Прямое чтение данных из памяти по заданным адресам.
- Возможность указания типов указателей, IQ-масштабирования и форматов возвращаемых данных.
- Аналогично режиму "Watch", поддерживается постоянный опрос выбранных низкоуровневых переменных.
- Умное автодополнение имён переменных и полей структур.
Логирование в CSV
- Записывайте все полученные значения в файл формата CSV для последующего анализа.
- Легкое управление записью: запуск, остановка и сохранение данных в любой момент.
---
---
# Для разработчиков
### Структура проекта:
```bash
Src
├── build/
│ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller)
├── DebugVarEdit_GUI.py # Главное окно
├── tms_debugvar_term.py # Терминал DebugVarTerminal
├── tms_debugvar_lowlevel.py # Виджет для выбора переменных для LowLevel Watch
├── 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
├── allvars_xml_parser.py # Парсинг XML со всеми переменными и структурами
├── csv_logger.py # Логирование переменных в CSV
├── myXML.py # Утилиты для XML
├── makefile_parser.py # Парсинг makefile на .c/.h файлы
├── var_setup.py # Подготовка переменных для окна выбора переменных
├── path_hints.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__`
- `<MAIN_SCRIPT_NAME>.*`
> Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`.
### Установка зависимостей
```bash
pip install clang PySide2 lxml nuitka pyinstaller
```

View File

@@ -1,27 +1,30 @@
# 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 os
import subprocess
import xml.etree.ElementTree as ET
from generateVars import type_map
import lxml.etree as ET
from generate_debug_vars import type_map, choose_type_map
from enum import IntEnum
from tms_debugvar_term import _DemoWindow
import threading
from generateVars import run_generate
import setupVars
from VariableSelector import VariableSelectorDialog
from VariableTable import VariableTableWidget, rows
from scanVarGUI import ProcessOutputWindow
import scanVars
from generate_debug_vars import run_generate
import var_setup
from var_selector_window import VariableSelectorDialog
from var_table import VariableTableWidget, rows
from scan_progress_gui import ProcessOutputWindow
import scan_vars
import myXML
import time
from PySide2.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem,
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
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.QtCore import Qt, QProcess, QObject, Signal, QSettings
@@ -31,12 +34,18 @@ var_edit_title = "Редактор переменных для отладки"
xml_path_title = "Путь к XML:"
proj_path_title = "Путь к проекту:"
makefile_path_title = "Пусть к makefile (относительно проекта)"
output_path_title = "Папка для debug_vars.c:"
output_path_title = "Путь для для debug_vars.c:"
scan_title = "Сканировать переменные"
build_title = "Сгенерировать файл"
add_vars_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: таблица с переменными
class VarEditor(QWidget):
def __init__(self):
@@ -52,12 +61,13 @@ class VarEditor(QWidget):
self.output_path = None
self._updating = False # Флаг блокировки рекурсии
self._resizing = False # флаг блокировки повторного вызова
self.target = 'TMS'
self.initUI()
def initUI(self):
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")
if os.path.exists(icon_path):
self.setWindowIcon(QIcon(icon_path))
@@ -116,6 +126,43 @@ class VarEditor(QWidget):
self.btn_update_vars = QPushButton(scan_title)
self.btn_update_vars.clicked.connect(self.update_vars_data)
# Добавляем чекбокс для выбора типовой карты
# --- Создаем верхнее меню ---
menubar = QMenuBar(self)
menubar.setToolTip('Разные размеры int и кодировки файлов')
self.target_menu = QMenu("МК:", menubar)
# Создаем действия для выбора Target
self.action_tms = QAction("TMS", self, checkable=True)
self.action_stm = QAction("STM", self, checkable=True)
# Инициализируем QSettings с именем организации и приложения
self.settings = QSettings("SET", "DebugVarEdit_MainWindow")
# Восстанавливаем сохранённое состояние, если есть
mcu = self.settings.value("mcu_choosen", True, type=str)
self.on_target_selected(mcu)
self.target_menu.setToolTip(f'TMS: Размер int 16 бит. Кодировка cp1251\nSTM: Размер int 32 бита. Кодировка utf-8')
# Группируем действия чтобы выбирался только один
self.action_tms.triggered.connect(lambda: self.on_target_selected("TMS"))
self.action_tms.setToolTip('Размер int 16 бит. Кодировка cp1251')
self.action_stm.triggered.connect(lambda: self.on_target_selected("STM"))
self.action_stm.setToolTip('Размер int 32 бита. Кодировка utf-8')
self.target_menu.addAction(self.action_tms)
self.target_menu.addAction(self.action_stm)
self.terminal_menu = QMenu("Открыть Терминал", menubar)
self.action_terminal_tms = QAction("TMS DemoTerminal", self)
self.action_terminal_modbus = QAction("Modbus DemoTerminal", self)
self.action_terminal_tms.triggered.connect(lambda: self.open_terminal("TMS"))
self.action_terminal_modbus.triggered.connect(lambda: self.open_terminal("MODBUS"))
self.terminal_menu.addAction(self.action_terminal_tms)
#self.terminal_menu.addAction(self.action_terminal_modbus)
menubar.addMenu(self.target_menu)
menubar.addMenu(self.terminal_menu)
# Кнопка сохранения
btn_save = QPushButton(build_title)
btn_save.clicked.connect(self.save_build)
@@ -131,6 +178,7 @@ class VarEditor(QWidget):
self.table = VariableTableWidget()
# Основной layout
layout = QVBoxLayout()
layout.setMenuBar(menubar) # прикрепляем menubar в layout сверху
layout.addLayout(xml_layout)
layout.addLayout(proj_layout)
layout.addLayout(makefile_layout)
@@ -144,6 +192,53 @@ class VarEditor(QWidget):
self.setLayout(layout)
def open_terminal(self, target):
target = target.lower()
if target == "tms":
exe_name = "DebugVarTerminal.exe"
# Путь к exe в текущей директории запуска программы
exe_path = os.path.join(os.getcwd(), exe_name)
if not os.path.isfile(exe_path):
# Файл не найден — попросим пользователя выбрать путь к exe
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setWindowTitle("Файл не найден")
msg.setText(f"Файл {exe_name} не найден в текущей папке.\nВыберите путь к {exe_name}.")
msg.exec_()
# Открываем диалог выбора файла
selected_path, _ = QFileDialog.getOpenFileName(
None, "Выберите файл " + exe_name, os.getcwd(), "Executable Files (*.exe)"
)
if not selected_path:
# Пользователь отменил выбор — ничего не делаем
return
exe_path = selected_path
# Запускаем exe (отдельное окно терминала)
subprocess.Popen([exe_path], creationflags=subprocess.CREATE_NEW_CONSOLE)
elif target == "modbus":
a = 1
def on_target_selected(self, target):
self.target_menu.setTitle(f'МК: {target}')
self.settings.setValue("mcu_choosen", target)
self.target = target.lower()
if self.target == "stm":
choose_type_map(True)
self.action_stm.setChecked(True)
self.action_tms.setChecked(False)
else:
choose_type_map(False)
self.action_tms.setChecked(True)
self.action_stm.setChecked(False)
def get_xml_path(self):
xml_path = self.xml_output_edit.text().strip()
@@ -202,7 +297,7 @@ class VarEditor(QWidget):
# Создаём окно с кнопкой "Готово"
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()
@@ -222,8 +317,8 @@ class VarEditor(QWidget):
var_data = {
'name': name_edit.text(),
'type': 'pt_' + pt_type_combo.currentText(),
'iq_type': iq_combo.currentText(),
'return_type': ret_combo.currentText() if ret_combo.currentText() else 'int',
'iq_type': 't_' + iq_combo.currentText(),
'return_type': 't_' + ret_combo.currentText() if ret_combo.currentText() else 't_iq_none',
'short_name': short_name_edit.text(),
}
vars_out.append(var_data)
@@ -235,15 +330,15 @@ class VarEditor(QWidget):
return
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 успешно сгенерирован.")
self.update()
except Exception as e:
QMessageBox.critical(self, "Ошибка при генерации", str(e))
def update(self):
if self._updating:
def update(self, force=0):
if self._updating and (force==0):
return # Уже в процессе обновления — выходим, чтобы избежать рекурсии
self._updating = True
@@ -296,11 +391,11 @@ class VarEditor(QWidget):
structs_path_full = myXML.make_absolute_path(structs_path, self.proj_path)
if structs_path_full and os.path.isfile(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:
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)
except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
@@ -352,10 +447,20 @@ class VarEditor(QWidget):
super().keyPressEvent(event)
def __browse_makefile(self):
if self.target == 'stm':
file_filter = "Makefile или Keil-проект (*.uvprojx *.uvproj makefile);;Все файлы (*)"
dialog_title = "Выберите Makefile или Keil-проект"
else: # 'TMS' или по умолчанию
file_filter = "Makefile (makefile);;Все файлы (*)"
dialog_title = "Выберите Makefile"
file_path, _ = QFileDialog.getOpenFileName(
self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)"
self,
dialog_title,
filter=file_filter
)
if file_path and self.proj_path:
if file_path:
if self.proj_path:
path = myXML.make_relative_path(file_path, self.proj_path)
else:
path = file_path
@@ -398,37 +503,42 @@ class VarEditor(QWidget):
self.update()
def __after_scanvars_finished(self):
self.update_all_paths()
def __after_scan_vars_finished(self):
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}")
return
try:
self.update(1)
except Exception as e:
self.makefile_path = None
self.structs_path = None
self.proj_path = None
self.update()
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}")
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
for row in selected_rows:
if 0 <= row < len(self.vars_list):
# Меняем флаг show_var для переменной с этим индексом
self.vars_list[row]['show_var'] = 'false'
# Меняем флаг show_var по имени
for var in self.vars_list:
if var.get('name') in selected_names:
var['show_var'] = 'false'
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
self.write_to_xml()
def __open_variable_selector(self):
self.update()
if not self.vars_list:
QMessageBox.warning(self, "Нет переменных", f"Сначала загрузите переменные ({scan_title}).")
return
@@ -438,9 +548,20 @@ class VarEditor(QWidget):
self.write_to_xml()
self.update()
def write_to_xml(self, dummy=None):
t0 = time.time()
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):
print("XML файл не найден или путь пустой")
@@ -465,7 +586,6 @@ class VarEditor(QWidget):
if self.makefile_path and os.path.isfile(self.makefile_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):
root.set("makefile_path", rel_makefile)
@@ -476,6 +596,7 @@ class VarEditor(QWidget):
if not os.path.isabs(rel_struct):
root.set("structs_path", rel_struct)
t1 = time.time()
vars_elem = root.find('variables')
if vars_elem is None:
@@ -490,6 +611,7 @@ class VarEditor(QWidget):
'extern': var_elem.findtext('extern', ''),
'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()}
@@ -498,59 +620,73 @@ class VarEditor(QWidget):
# Объединённый список переменных для записи
all_names = list(all_vars_by_name.keys())
t2 = time.time()
for name in all_names:
v = all_vars_by_name[name]
v_table = table_vars.get(name)
var_elem = None
# Ищем уже существующий <var> в XML
for ve in vars_elem.findall('var'):
if ve.attrib.get('name') == name:
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)
pt_type_val = get_val('pt_type').lower()
if 'arr' in pt_type_val or 'struct' in pt_type_val or 'union' in pt_type_val:
continue
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()
set_sub_elem_text(var_elem, 'show_var', show_var_val)
set_sub_elem_text(var_elem, 'enable', enable_val)
enable_val = get_val('enable').lower()
# Тут подтягиваем из таблицы, если есть, иначе из v
shortname_val = v_table['shortname'] if v_table and 'shortname' in v_table else v.get('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 = v_table['iq_type'] if v_table and 'iq_type' in v_table else v.get('iq_type', '')
ret_type_val = v_table['return_type'] if v_table and 'return_type' in v_table else v.get('return_type', '')
shortname_val = get_val('shortname')
iq_type_val = get_val('iq_type')
ret_type_val = get_val('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_val = v.get('file') or original_info.get(name, {}).get('file', '')
extern_val = v.get('extern') or original_info.get(name, {}).get('extern', '')
static_val = v.get('static') or original_info.get(name, {}).get('static', '')
set_sub_elem_text(var_elem, 'file', file_val)
set_sub_elem_text(var_elem, 'extern', extern_val)
set_sub_elem_text(var_elem, 'static', static_val)
values_to_write = {
'show_var': show_var_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)
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:
print(f"Ошибка при сохранении XML: {e}")
def __open_output_file_with_program(self):
output_path = self.get_output_path()
if not output_path:

62
Src/README_DEVELOP.md Normal file
View File

@@ -0,0 +1,62 @@
# Для разработчиков
### Структура проекта:
```bash
Src
├── build/
│ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller)
├── DebugVarEdit_GUI.py # Главное окно
├── tms_debugvar_term.py # Терминал DebugVarTerminal
├── tms_debugvar_lowlevel.py # Виджет для выбора переменных для LowLevel Watch
├── 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
├── allvars_xml_parser.py # Парсинг XML со всеми переменными и структурами
├── csv_logger.py # Логирование переменных в CSV
├── myXML.py # Утилиты для XML
├── makefile_parser.py # Парсинг makefile на .c/.h файлы
├── var_setup.py # Подготовка переменных для окна выбора переменных
├── path_hints.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__`
- `<MAIN_SCRIPT_NAME>.*`
> Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`.
### Установка зависимостей
```bash
pip install clang 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 "")

500
Src/allvars_xml_parser.py Normal file
View File

@@ -0,0 +1,500 @@
"""
VariablesXML + get_all_vars_data
---------------------------------
Поддержка вложенных структур, указателей на структуры ("->"),
и многомерных массивов (индексация [i][j]...).
Требования пользователя:
- size (без индекса) = общий размер массива в байтах (НЕ измерение!).
- size1..sizeN = размеры измерений массива.
- В результирующем плоском списке (flattened) должны присутствовать ВСЕ промежуточные
пути: var, var[0], var[0][0], var[0][0].field, var[0][0].field->subfield, ...
- Аналогично для членов структур.
Пример желаемого формата:
project
project.adc
project.adc[0]
project.adc[0][0]
project.adc[0][0].bus
project.adc[0][0].bus->status
Данный модуль реализует:
- Разбор XML (parse) с извлечением размеров размерностей в поле `dims`.
- Генерацию плоского списка словарей `flattened()`.
- Построение иерархии словарей `get_all_vars_data()`.
"""
from __future__ import annotations
import re
import xml.etree.ElementTree as ET
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple, Any
import var_setup # ожидается split_path(...)
from generate_debug_vars import choose_type_map, type_map # используется для выбора карт типов
# --------------------------- константы ----------------------------
DATE_FIELD_SET = {"year", "month", "day", "hour", "minute"}
# --------------------------- dataclasses --------------------------
@dataclass
class MemberNode:
name: str
offset: int = 0
type_str: str = ""
size: Optional[int] = None # общий размер (байты), если известен
children: List["MemberNode"] = field(default_factory=list)
# --- доп.поля ---
kind: Optional[str] = None # 'array', 'union', ...
dims: Optional[List[int]] = None
def is_date_struct(self) -> bool:
if not self.children:
return False
child_names = {c.name for c in self.children}
return DATE_FIELD_SET.issubset(child_names)
@dataclass
class VariableNode:
name: str
address: int
type_str: str
size: Optional[int]
members: List[MemberNode] = field(default_factory=list)
# --- доп.поля ---
kind: Optional[str] = None # 'array'
dims: Optional[List[int]] = None # полный список размеров [size1, size2, ...]
def base_address_hex(self) -> str:
return f"0x{self.address:06X}"
# --------------------------- класс парсера -----------------------
class VariablesXML:
"""
Читает XML и предоставляет методы:
- flattened(): плоский список всех путей.
- date_struct_candidates(): как раньше.
Правила формирования путей:
* Структурные поля: '.'
* Поля через указатель на структуру: '->'
* Массивы: [index] (каждое измерение).
"""
# предполагаемые размеры примитивов (MCU: int=2)
_PRIM_SIZE = {
'char': 1, 'signed char': 1, 'unsigned char': 1,
'uint8_t': 1, 'int8_t': 1,
'short': 2, 'short int': 2, 'signed short': 2, 'unsigned short': 2,
'uint16_t': 2, 'int16_t': 2,
'int': 2, 'signed int': 2, 'unsigned int': 2,
'long': 4, 'unsigned long': 4, 'int32_t': 4, 'uint32_t': 4,
'float': 4,
'long long': 8, 'unsigned long long': 8, 'int64_t': 8, 'uint64_t': 8, 'double': 8,
}
def __init__(self, path: str):
self.path = path
self.timestamp: str = ""
self.variables: List[VariableNode] = []
choose_type_map(0) # инициализация карт типов (если требуется)
self._parse()
# ------------------ утилиты ------------------
@staticmethod
def _parse_int_guess(txt: Optional[str]) -> Optional[int]:
if not txt:
return None
txt = txt.strip()
if txt.startswith(('0x', '0X')):
return int(txt, 16)
# если в строке есть A-F, попробуем hex
if any(c in 'abcdefABCDEF' for c in txt):
try:
return int(txt, 16)
except ValueError:
pass
try:
return int(txt, 10)
except ValueError:
return None
@staticmethod
def _is_pointer_to_struct(t: str) -> bool:
if not t:
return False
low = t.replace('\t', ' ').replace('\n', ' ')
return 'struct ' in low and '*' in low
@staticmethod
def _is_struct_or_union(t: str) -> bool:
if not t:
return False
low = t.strip()
return low.startswith('struct ') or low.startswith('union ')
@staticmethod
def _is_union(t: str) -> bool:
if not t:
return False
low = t.strip()
return low.startswith('union ')
@staticmethod
def _strip_array_suffix(t: str) -> str:
t = t.strip()
while t.endswith('[]'):
t = t[:-2].strip()
return t
def _guess_primitive_size(self, type_str: str) -> Optional[int]:
if not type_str:
return None
base = type_str
for tok in ('volatile', 'const'):
base = base.replace(tok, '')
base = base.replace('*', ' ')
base = base.replace('[', ' ').replace(']', ' ')
base = ' '.join(base.split()).strip()
return self._PRIM_SIZE.get(base)
# ------------------ XML read ------------------
def _parse(self) -> None:
try:
tree = ET.parse(self.path)
root = tree.getroot()
ts = root.find('timestamp')
self.timestamp = ts.text.strip() if ts is not None and ts.text else ''
def parse_member(elem: ET.Element, base_offset=0) -> MemberNode:
name = elem.get('name', '')
offset = int(elem.get('offset', '0'), 16) if elem.get('offset') else 0
t = elem.get('type', '') or ''
size_attr = elem.get('size')
size = int(size_attr, 16) if size_attr else None
kind = elem.get('kind')
abs_offset = base_offset + offset
# Собираем размеры, если есть
dims: List[int] = []
i = 1
while True:
size_key = f"size{i}"
size_val = elem.get(size_key)
if size_val is None:
break
parsed = self._parse_int_guess(size_val) # предполагается твоя функция парсинга int
if parsed is not None:
dims.append(parsed)
i += 1
node = MemberNode(
name=name,
offset=abs_offset,
type_str=t,
size=size,
kind=kind,
dims=dims if dims else None,
)
# Для детей
for ch in elem.findall('member'):
if kind == 'union':
# Для union детей НЕ добавляем их offset, просто передаём abs_offset
child = parse_member(ch, base_offset=abs_offset)
child.offset = abs_offset # выравниваем offset, игнорируем offset детей
else:
# Для struct/array суммируем offset нормально
child = parse_member(ch, base_offset=abs_offset)
node.children.append(child)
# Аналогично для pointee
pointee_elem = elem.find('pointee')
if pointee_elem is not None:
for ch in pointee_elem.findall('member'):
if kind == 'union':
child = parse_member(ch, base_offset=abs_offset)
child.offset = abs_offset
else:
child = parse_member(ch, base_offset=abs_offset)
node.children.append(child)
size_p = pointee_elem.get('size')
if size_p:
node.size = int(size_p, 16)
return node
for var in root.findall('variable'):
addr = int(var.get('address', '0'), 16)
name = var.get('name', '')
t = var.get('type', '') or ''
size_attr = var.get('size') # общий размер байт
size = int(size_attr, 16) if size_attr else None
kind = var.get('kind')
dims: List[int] = []
i = 1
while True:
key = f'size{i}'
val = var.get(key)
if val is None:
break
parsed = self._parse_int_guess(val)
if parsed is not None:
dims.append(parsed)
i += 1
members = [parse_member(m) for m in var.findall('member')]
v = VariableNode(
name=name,
address=addr,
type_str=t,
size=size,
members=members,
kind=kind,
dims=dims if dims else None,
)
self.variables.append(v)
except FileNotFoundError:
self.variables = []
except ET.ParseError:
self.variables = []
# ------------------ helpers для flattened ---------------------
def _elem_size_bytes(self, total_size: Optional[int], dims: List[int], base_type: str, members: List[MemberNode]) -> int:
"""Оценка размера одного *листового* элемента (последнего измерения).
Если total_size и dims все известны — берём size / prod(dims).
Иначе — пробуем примитивный размер; иначе 1.
(Не учитываем выравнивание структур; при необходимости можно расширить.)
"""
if total_size is not None and dims:
prod = 1
for d in dims:
if d is None or d == 0:
prod = None
break
prod *= d
if prod and prod > 0:
return max(1, total_size // prod)
prim = self._guess_primitive_size(base_type)
if prim:
return prim
# Если структура и у неё есть size по детям? Пока fallback=1.
return 1
# ------------------ flattened ------------------
def flattened(self, max_array_elems: Optional[int] = None) -> List[Dict[str, Any]]:
"""Возвращает плоский список всех путей (каждый путь = dict).
Включает промежуточные узлы массивов (var[0], var[0][0], ...).
"""
out: List[Dict[str, Any]] = []
def mk(name: str, addr: Optional[int], type_str: str, size: Optional[int], kind: Optional[str], dims_for_node: Optional[List[int]]):
if 'Bender' in name:
a=1
out.append({
'name': name,
'address': addr,
'type': type_str,
'size': size,
'kind': kind,
'dims': dims_for_node[:] if dims_for_node else None,
})
def expand_members(prefix: str, base_addr: int, members: List[MemberNode], parent_is_ptr_struct: bool, parent_is_union: bool) -> None:
# Выбираем разделитель пути: '.' если обычный член, '->' если указатель на структуру
join = '->' if parent_is_ptr_struct else '.'
for m in members:
path_m = f"{prefix}{join}{m.name}" if prefix else m.name
is_union = m.kind == 'union' or parent_is_union
if is_union:
# Все поля union начинаются с одного адреса
addr_m = base_addr
else:
addr_m = base_addr + m.offset
dims = m.dims or []
mk(path_m, addr_m, m.type_str, m.size, m.kind, dims)
if m.kind == 'array' and dims:
base_t = self._strip_array_suffix(m.type_str)
elem_sz = m.size
# Для массива внутри структуры: первый уровень — '.' для доступа,
# внутри массива раскрываем по обычной логике с parent_is_ptr_struct=False
expand_dims(path_m, addr_m, dims, base_t, m.children, elem_sz, parent_is_ptr_struct=False)
else:
if m.children:
# Проверяем, является ли поле указателем на структуру
is_ptr = self._is_pointer_to_struct(m.type_str)
# Рекурсивно раскрываем дочерние поля, выбирая правильный разделитель
expand_members(path_m, addr_m, m.children, is_ptr, is_union)
def expand_dims(name: str, base_addr: int, dims: List[int], base_type: str, children: List[MemberNode], elem_size: int, parent_is_ptr_struct: bool) -> None:
prods: List[int] = []
acc = 1
for d in reversed(dims[1:]):
acc *= (d if d else 1)
prods.append(acc)
prods.reverse()
def rec(k: int, cur_name: str, cur_addr: int) -> None:
if k == len(dims):
# Листовой элемент массива
mk(cur_name, cur_addr, base_type, elem_size, None, None)
# Если элемент — структура или указатель на структуру, раскрываем вложения
if children and self._is_struct_or_union(base_type):
expand_members(cur_name, cur_addr, children, parent_is_ptr_struct=False, parent_is_union=self._is_union(base_type))
elif self._is_pointer_to_struct(base_type):
expand_members(cur_name, cur_addr, children, parent_is_ptr_struct=True, parent_is_union=self._is_union(base_type))
return
dim_sz = dims[k] or 0
if max_array_elems is not None:
dim_sz = min(dim_sz, max_array_elems)
stride = elem_size * prods[k] if k < len(prods) else elem_size
if len(dims) > 2:
a=1
for i in range(dim_sz):
child_name = f"{cur_name}[{i}]"
child_addr = (cur_addr + i * stride) if cur_addr is not None else None
remaining = dims[k+1:]
mk(child_name, child_addr, base_type + '[]' * len(remaining), stride if remaining else elem_size, 'array' if remaining else None, remaining)
rec(k + 1, child_name, child_addr)
rec(0, name, base_addr)
# --- цикл по топ‑левел переменным ---
for v in self.variables:
dims = v.dims or []
mk(v.name, v.address, v.type_str, v.size, v.kind, dims)
if (v.kind == 'array' or v.type_str.endswith('[]')) and dims:
base_t = self._strip_array_suffix(v.type_str)
elem_sz = v.size
expand_dims(v.name, v.address, dims, base_t, v.members, elem_sz, parent_is_ptr_struct=False)
else:
if v.members:
is_ptr = self._is_pointer_to_struct(v.type_str)
is_union = self._is_union(v.type_str)
expand_members(v.name, v.address, v.members, is_ptr, is_union)
return out
# -------------------- date candidates (как раньше) -------------
def date_struct_candidates(self) -> List[Tuple[str, int]]:
cands = []
for v in self.variables:
# top level (if all date fields are present)
direct_names = {mm.name for mm in v.members}
if DATE_FIELD_SET.issubset(direct_names):
cands.append((v.name, v.address))
# check first-level members
for m in v.members:
if m.is_date_struct():
cands.append((f"{v.name}.{m.name}", v.address + m.offset))
return cands
# ------------------------------------------------------------------
# Построение иерархического дерева из flattened()
# ------------------------------------------------------------------
def get_all_vars_data(self) -> List[Dict[str, Any]]:
"""
Строит иерархию словарей из плоского списка переменных.
Каждый узел = {
'name': <полный путь>,
'address': <адрес или None>,
'type': <тип>,
'size': <байты>,
'kind': <'array' | ...>,
'dims': [size1, size2, ...] или None,
'children': [...список дочерних узлов]
}
Возвращает список корневых узлов (top-level переменных).
"""
flat_data = self.flattened(max_array_elems=None)
# Быстрое отображение имя -> узел (словарь с детьми)
all_nodes: Dict[str, Dict[str, Any]] = {}
for item in flat_data:
node = dict(item)
node['children'] = []
all_nodes[item['name']] = node
def _parent_struct_split(path: str) -> Optional[str]:
# Ищем последний '.' или '->' для определения родителя
dot_idx = path.rfind('.')
arrow_idx = path.rfind('->')
cut_idx = max(dot_idx, arrow_idx)
if cut_idx == -1:
return None
# '->' занимает 2 символа, нужно взять срез до начала '->'
if arrow_idx > dot_idx:
return path[:arrow_idx]
else:
return path[:dot_idx]
def find_parent(path: str) -> Optional[str]:
"""
Возвращает полный путь родителя, учитывая '.', '->' и индексы [] в конце.
Если путь заканчивается индексом [k], удаляет последний индекс и проверяет наличие родителя.
Иначе пытается найти последний сепаратор '.' или '->'.
"""
# Если есть trailing индекс в конце, убираем его
m = re.search(r'\[[0-9]+\]$', path)
if m:
base = path[:m.start()] # убираем последний [k]
# Если базовый путь есть в узлах, считаем его родителем
if base in all_nodes:
return base
# Иначе пытаемся найти родителя от базового пути
return _parent_struct_split(base)
else:
# Если нет индекса, просто ищем последний разделитель
return _parent_struct_split(path)
# Строим иерархию: parent -> children
roots: List[Dict[str, Any]] = []
for full_name, node in all_nodes.items():
parent_name = find_parent(full_name)
if parent_name and parent_name in all_nodes:
all_nodes[parent_name]['children'].append(node)
else:
roots.append(node)
# Рекурсивно сортируем детей по имени для порядка
def sort_nodes(nodes: List[Dict[str, Any]]):
nodes.sort(key=lambda n: n['name'])
for n in nodes:
if n['children']:
sort_nodes(n['children'])
sort_nodes(roots)
return roots

View File

@@ -2,22 +2,22 @@ import subprocess
import shutil
import os
from pathlib import Path
from PIL import Image # нужна библиотека Pillow для конвертации PNG в ICO
import PySide2
from PyInstaller.utils.hooks import collect_data_files
# install:
# install: pip install PySide2 lxml nuitka pyinstaller
# - PyInstaller
# - nuitka
# - Pillow
# - PySide2
# - clang
# === Конфигурация ===
USE_NUITKA = True # True — сборка через Nuitka, False — через PyInstaller
MAIN_SCRIPT_NAME = "tms_debugvar_term"
OUTPUT_NAME = "DebugVarTerminal"
SRC_PATH = Path("./Src/")
SCRIPT_PATH = SRC_PATH / "DebugVarEdit_GUI.py"
OUTPUT_NAME = "DebugVarEdit"
SCRIPT_PATH = SRC_PATH / (MAIN_SCRIPT_NAME + ".py")
DIST_PATH = Path("./").resolve()
WORK_PATH = Path("./build_temp").resolve()
@@ -25,6 +25,13 @@ SPEC_PATH = WORK_PATH
ICON_PATH = SRC_PATH / "icon.png"
ICON_ICO_PATH = SRC_PATH / "icon.ico"
TEMP_FOLDERS = [
"build_temp",
"__pycache__",
MAIN_SCRIPT_NAME + ".build",
MAIN_SCRIPT_NAME + ".onefile-build",
MAIN_SCRIPT_NAME + ".dist"
]
# === Пути к DLL и прочим зависимостям ===
LIBS = {
"libclang.dll": SRC_PATH / "libclang.dll"
@@ -48,7 +55,7 @@ for name, path in LIBS.items():
print(f"WARNING: {path.name} не найден — он не будет включён в сборку")
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)
if path.exists():
shutil.rmtree(path, ignore_errors=True)

224
Src/csv_logger.py Normal file
View File

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

View File

@@ -1,20 +1,21 @@
# build command
# pyinstaller --onefile --distpath . --workpath ./build --specpath ./build generateVars.py
# pyinstaller --onefile --distpath . --workpath ./build --specpath ./build generate_debug_vars.py
# 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 os
import re
import xml.etree.ElementTree as ET
import lxml.etree as ET
from pathlib import Path
from xml.dom import minidom
import myXML
import argparse
shortnameSize = 10
# === Словарь соответствия типов XML → DebugVarType_t ===
type_map = dict([
type_map_tms = dict([
*[(k, 'pt_int8') for k in ('signed char', 'char')],
*[(k, 'pt_int16') for k in ('int', 'int16', 'short')],
*[(k, 'pt_int32') for k in ('long', 'int32', '_iqx')],
@@ -52,6 +53,150 @@ type_map = dict([
('struct[]', 'pt_arr_struct'),
('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):
typename_orig = typename.strip()
@@ -140,15 +285,20 @@ def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
Возвращает 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 = {}
if os.path.isfile(output_path):
with open(output_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
# {(char *)&some.deep.var.name , pt_uint16 , t_iq15 , "ShortName"},
m = re.match(
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)
# {(uint8_t *)&some.deep.var.name , pt_uint16 , t_iq15 , t_iq10, "ShortName"},
m = pattern.search(line)
if m:
full_varname = m.group(1) # e.g., some.deep.var.name
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
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):
output_dir = os.path.join(proj_path, output_dir)
os.makedirs(output_dir, exist_ok=True)
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
@@ -338,7 +503,7 @@ def generate_vars_file(proj_path, xml_path, output_dir):
# Дополнительные поля, например комментарий
comment = info.get("comment", "")
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'):
f_name = f'{vname},'
@@ -348,7 +513,7 @@ def generate_vars_file(proj_path, xml_path, output_dir):
f_short_name = f'"{short_trimmed}"' # оборачиваем в кавычки
# Добавим комментарий после записи, если он есть
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
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'int DebugVar_Qnt = {len(all_debug_lines)};')
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.extend(all_debug_lines)
out_lines.append('};')
out_lines.append('')
# Выберем кодировку для записи файла
# Если встречается несколько, возьмем первую из set
if stm_flag_global == 0:
enc_to_write = 'cp1251'
else:
enc_to_write = 'utf-8'
#print("== GLOBAL VARS FOUND ==")
#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:
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)}')
@@ -472,16 +652,17 @@ Usage example:
print(f"Error: Project path '{proj_path}' не является директорией или не существует.")
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__":
main()
def run_generate(proj_path, xml_path, output_dir):
def run_generate(proj_path, xml_path, output_dir, shortname_size):
import os
global shortnameSize
shortnameSize = shortname_size
# Normalize absolute paths
proj_path = os.path.abspath(proj_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 xml.etree.ElementTree as ET
from lxml import etree
def make_absolute_path(path, 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("\\", "/")
def indent_xml(elem, level=0):
indent = " "
i = "\n" + level * indent
# Убираем strip() — они медленные, и текст мы всё равно перезаписываем
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + indent
elem.text = elem.text or i + " "
for child in elem:
indent_xml(child, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
elem[-1].tail = elem[-1].tail or i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
elem.tail = elem.tail or i
def fwrite(root, xml_full_path):
indent_xml(root)
ET.ElementTree(root).write(xml_full_path, encoding="utf-8", xml_declaration=True)
#indent_xml(root)
#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) при ошибках.
"""
if not xml_path or not os.path.isfile(xml_path):
print(f"Файл '{xml_path}' не найден или путь пустой")
return None, None
try:
if os.path.getsize(xml_path) == 0:
return None, None
tree = ET.parse(xml_path)
tree = etree.parse(xml_path)
root = tree.getroot()
return root, tree
except ET.ParseError as e:
except etree .XMLSyntaxError as e:
print(f"Ошибка парсинга XML файла '{xml_path}': {e}")
return None, None
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)

319
Src/path_hints.py Normal file
View File

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

View File

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

View File

@@ -1,7 +1,7 @@
# 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
# 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 sys
@@ -9,9 +9,9 @@ import re
import clang.cindex
from clang import cindex
from clang.cindex import Config
import xml.etree.ElementTree as ET
import lxml.etree as ET
from xml.dom import minidom
from parseMakefile import parse_makefile
from makefile_parser import parse_project
from collections import deque
import argparse
import myXML
@@ -118,11 +118,11 @@ def get_canonical_typedef_file(var_type, include_dirs):
break
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...")
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 = {} # имя переменной → словарь с инфой
h_files_needed = set()
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))
if node.kind == clang.cindex.CursorKind.VAR_DECL:
if node.semantic_parent.kind == clang.cindex.CursorKind.TRANSLATION_UNIT:
is_extern = (node.storage_class == clang.cindex.StorageClass.EXTERN)
@@ -171,6 +172,11 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
if node.spelling == 'HUGE': # еще одна служеюная, которую хз как выделять
return
if 'Drivers' in node.location.file.name:
return
if 'uint' in node.spelling:
a = 1
# Проверяем, является ли тип указателем на функцию
# Признак: в типе есть '(' и ')' и '*', например: "void (*)(int)"
if "(" in var_type and "*" in var_type and ")" in var_type:
@@ -295,6 +301,8 @@ def strip_ptr_and_array(typename):
return typename
def analyze_typedefs_and_struct(typedefs, structs):
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
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...")
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_structs_raw = {}
@@ -452,7 +478,6 @@ def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
raw_name = node.spelling
normalized_name = normalize_type_name(raw_name)
# struct_name всегда с префиксом
if node.spelling and "unnamed" not in 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),
'pt_type': var_elem.findtext('pt_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'),
'file': var_elem.findtext('file', ''),
'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),
'pt_type': info.get('pt_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'),
'file': info.get('file', ''),
'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, "pt_type").text = info.get('pt_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')
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.")
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)
typedefs, structs = analyze_typedefs_and_structs_across_files(c_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, global_defs)
vars = dict(sorted(vars.items()))
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):
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)
typedefs, structs = analyze_typedefs_and_structs_across_files(c_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, global_defs)
vars = dict(sorted(vars.items()))
includes = get_sorted_headers(c_files, includes, include_dirs)

View File

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

1268
Src/tms_debugvar_term.py Normal file

File diff suppressed because it is too large Load Diff

428
Src/var_selector_table.py Normal file
View File

@@ -0,0 +1,428 @@
# variable_select_widget.py
import pickle
import hashlib
from typing import List, Dict, Any, Optional
from PySide2.QtWidgets import (
QWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLineEdit,
QHeaderView, QCompleter
)
from PySide2.QtGui import QKeyEvent
from PySide2.QtCore import Qt, QStringListModel
from path_hints import PathHints, canonical_key, split_path_tokens
# ------------------------------------------------------------------
# utils
# ------------------------------------------------------------------
def compute_vars_hash(vars_list):
return hashlib.sha1(pickle.dumps(vars_list)).hexdigest()
def is_lazy_item(item: QTreeWidgetItem) -> bool:
return item.childCount() == 1 and item.child(0).text(0) == 'lazy_marker'
# ------------------------------------------------------------------
# VariableSelectWidget
# ------------------------------------------------------------------
class VariableSelectWidget(QWidget):
"""
Виджет выбора переменных с деревом + строкой поиска + автодополнением.
Подсказки полностью через PathHints.
ВАЖНО: ожидается, что в данных (vars_list) каждое var['name'] — ПОЛНЫЙ ПУТЬ
(например: 'project.adc.status'), даже внутри children.
"""
ROLE_NAME = Qt.UserRole # локальный хвост (display)
ROLE_VAR_DICT = Qt.UserRole + 100 # исходный dict
ROLE_FULLPATH = Qt.UserRole + 200 # полный путь
def __init__(self, parent=None):
super().__init__(parent)
# данные
self.expanded_vars: List[Dict[str, Any]] = []
self.is_autocomplete_on = True
self.manual_completion_active = False
self._bckspc_pressed = False
self._vars_hash: Optional[str] = None
# индекс: canonical_full_path -> item
self._item_by_canon: Dict[str, QTreeWidgetItem] = {}
# подсказки
self.hints = PathHints()
# --- UI ---
self.search_input = QLineEdit(self)
self.search_input.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)
self.completer.activated[str].connect(self.insert_completion)
# layout
lay = QVBoxLayout(self)
lay.setContentsMargins(0, 0, 0, 0)
lay.addWidget(self.search_input)
lay.addWidget(self.tree)
# signals
self.search_input.textChanged.connect(self.on_search_text_changed)
self.search_input.installEventFilter(self)
# ------------------------------------------------------------------
# public api
# ------------------------------------------------------------------
def set_autocomplete(self, enabled: bool):
self.is_autocomplete_on = enabled
def set_data(self, vars_list: List[Dict[str, Any]]):
"""
Загружаем список переменных (формат: см. класс docstring).
"""
# deepcopy
self.expanded_vars = pickle.loads(pickle.dumps(vars_list, protocol=pickle.HIGHEST_PROTOCOL))
# rebuild hints из полного списка узлов (каждый узел уже с full_path)
self._rebuild_hints_from_vars(self.expanded_vars)
# rebuild tree
self.populate_tree(self.expanded_vars)
# ------------------------------------------------------------------
# hints builder: дети уже содержат ПОЛНЫЙ ПУТЬ
# ------------------------------------------------------------------
def _rebuild_hints_from_vars(self, vars_list: List[Dict[str, Any]]):
paths: List[tuple] = []
def walk(node: Dict[str, Any]):
full = node.get('name', '')
if full:
paths.append((full, node.get('type')))
for ch in node.get('children', []) or []:
walk(ch)
for v in vars_list:
walk(v)
self.hints.set_paths(paths)
# ------------------------------------------------------------------
# tree building
# ------------------------------------------------------------------
def populate_tree(self, vars_list=None):
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._item_by_canon.clear()
# построим top-level из входного списка: определяем по глубине токенов
# (vars_list может содержать и глубокие узлы; выберем корни = те, чей full_path не имеет родителя в списке)
full_to_node = {v['name']: v for v in vars_list}
# но safer: просто добавляем все как top-level, если ты уже передаёшь только корни.
# Если в твоих данных vars_list == корни, просто сделаем:
for v in vars_list:
self._add_tree_item_lazy(None, v)
self.tree.setUpdatesEnabled(True)
self.tree.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: QTreeWidgetItem):
if is_lazy_item(item):
item.removeChild(item.child(0))
var = item.data(0, self.ROLE_VAR_DICT)
if var:
for ch in var.get('children', []) or []:
self._add_tree_item_lazy(item, ch)
# ------------------------------------------------------------------
# item creation (var['name'] — ПОЛНЫЙ ПУТЬ)
# ------------------------------------------------------------------
def _add_tree_item_lazy(self, parent: Optional[QTreeWidgetItem], var: Dict[str, Any]):
full_path = var.get('name', '')
type_str = var.get('type', '')
# здесь оставляем полный путь для отображения
item = QTreeWidgetItem([full_path, type_str])
item.setData(0, self.ROLE_NAME, full_path) # теперь ROLE_NAME = полный путь
item.setData(0, self.ROLE_VAR_DICT, var)
item.setData(0, self.ROLE_FULLPATH, full_path)
if "(bitfield:" in type_str:
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)
# lazy children
if var.get('children'):
dummy = QTreeWidgetItem(["lazy_marker"])
item.addChild(dummy)
# индекс
self._item_by_canon[canonical_key(full_path)] = item
@staticmethod
def _tail_token(full_path: str) -> str:
toks = split_path_tokens(full_path)
return toks[-1] if toks else full_path
# ------------------------------------------------------------------
# filtering
# ------------------------------------------------------------------
def filter_tree(self):
"""
Быстрый фильтр:
- без разделителей → substring по ЛОКАЛЬНОМУ имени top-level
- с разделителями → структурный (по токенам full_path)
"""
text = (self.search_input.text() or '').strip()
low = text.lower()
parts = split_path_tokens(low) if low else []
# простой режим (нет ., ->, [):
if low and all(x not in low for x in ('.', '->', '[')):
for i in range(self.tree.topLevelItemCount()):
it = self.tree.topLevelItem(i)
full = (it.data(0, self.ROLE_FULLPATH) or '').lower()
it.setHidden(low not in full)
return
# структурный
for i in range(self.tree.topLevelItemCount()):
it = self.tree.topLevelItem(i)
self._show_matching_path(it, parts, 0)
def _show_matching_path(self, item: QTreeWidgetItem, path_parts: List[str], level: int = 0):
"""
Сравниваем введённый путь (разбитый на токены) с ПОЛНЫМ ПУТЁМ узла.
Алгоритм: берём полный путь узла, разбиваем в токены, берём уровень level,
и сравниваем с соответствующим токеном path_parts[level].
"""
full = (item.data(0, self.ROLE_FULLPATH) or '').lower()
node_parts = split_path_tokens(full)
if level >= len(path_parts):
item.setHidden(False)
item.setExpanded(False)
return True
if level >= len(node_parts):
item.setHidden(True)
return 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()):
ch = item.child(i)
if self._show_matching_path(ch, 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
# ------------------------------------------------------------------
# completions (ONLY PathHints)
# ------------------------------------------------------------------
def update_completions(self, text: Optional[str] = None) -> List[str]:
if text is None:
text = self.search_input.text()
suggestions = self.hints.suggest(text)
self.completer.setModel(QStringListModel(suggestions))
if suggestions:
self.completer.complete()
else:
self.completer.popup().hide()
return suggestions
def insert_completion(self, full_path: str):
text = self.hints.add_separator(full_path)
if not self._bckspc_pressed:
self.search_input.setText(text)
self.search_input.setCursorPosition(len(text))
self.run_completions(text)
# ------------------------------------------------------------------
# events
# ------------------------------------------------------------------
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
self.run_completions(self.search_input.text())
elif event.key() == Qt.Key_Escape:
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: str):
if not self.is_autocomplete_on and not self.manual_completion_active:
self.completer.popup().hide()
return
self.update_completions(text)
def on_search_text_changed(self, text: str):
self.completer.setWidget(self.search_input)
self.filter_tree()
if text is None:
text = self.search_input.text()
if self.is_autocomplete_on:
self.run_completions(text)
else:
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 closeEvent(self, event):
self.completer.setWidget(None)
self.completer.deleteLater()
super().closeEvent(event)
# ------------------------------------------------------------------
# lookup by full path
# ------------------------------------------------------------------
def find_item_by_fullpath(self, path: str) -> Optional[QTreeWidgetItem]:
return self._item_by_canon.get(canonical_key(path))
# ------------------------------------------------------------------
# tooltips
# ------------------------------------------------------------------
def _set_tool(self, item: QTreeWidgetItem, text: str):
item.setToolTip(0, text)
item.setToolTip(1, text)
# ------------------------------------------------------------------
# selection helpers
# ------------------------------------------------------------------
def get_all_items(self):
"""Все leaf-узлы (подгружаем lazy)."""
def collect_leaf(parent):
leaves = []
for i in range(parent.childCount()):
ch = parent.child(i)
if ch.isHidden():
continue
self.on_item_expanded(ch)
if ch.childCount() == 0:
t = ch.text(1)
if t and 'bitfield' in t.lower():
continue
leaves.append(ch)
else:
leaves.extend(collect_leaf(ch))
return leaves
out = []
for i in range(self.tree.topLevelItemCount()):
top = self.tree.topLevelItem(i)
self.on_item_expanded(top)
if top.childCount() == 0:
t = top.text(1)
if t and 'bitfield' in t.lower():
continue
out.append(top)
else:
out.extend(collect_leaf(top))
return out
def _get_internal_selected_items(self):
selected = self.tree.selectedItems()
all_items = []
def collect(item):
self.on_item_expanded(item)
res = [item]
for i in range(item.childCount()):
res.extend(collect(item.child(i)))
return res
for it in selected:
all_items.extend(collect(it))
return all_items
def get_selected_items(self):
selected = self.tree.selectedItems()
leaves = []
for it in selected:
self.on_item_expanded(it)
if all(it.child(i).isHidden() or not it.child(i).isSelected() for i in range(it.childCount())):
t = it.data(0, self.ROLE_NAME)
if t and isinstance(t, str) and 'bitfield' in t.lower():
continue
leaves.append(it)
return leaves
def get_all_var_names(self):
return [it.data(0, self.ROLE_FULLPATH) for it in self.get_all_items() if it.data(0, self.ROLE_FULLPATH)]
def _get_internal_selected_var_names(self):
return [it.data(0, self.ROLE_FULLPATH) for it in self._get_internal_selected_items() if it.data(0, self.ROLE_FULLPATH)]
def get_selected_var_names(self):
return [it.data(0, self.ROLE_FULLPATH) for it in self.get_selected_items() if it.data(0, self.ROLE_FULLPATH)]

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': 't_iq_none',
'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 os
import re
import xml.etree.ElementTree as ET
from generateVars import map_type_to_pt, get_iq_define, type_map
import lxml.etree as ET
from generate_debug_vars import map_type_to_pt, get_iq_define, type_map
from enum import IntEnum
import scanVars
import scan_vars
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):
@@ -86,7 +138,7 @@ def parse_vars(filename, typedef_map=None):
'shortname': var.findtext('shortname', name),
'pt_type': pt_type,
'iq_type': iq_type,
'return_type': var.findtext('return_type', ''),
'return_type': var.findtext('return_type', 't_iq_none'),
'type': var_type,
'file': var.findtext('file', ''),
'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)
# Ищем структуру по имени типа
base_type = scanVars.strip_ptr_and_array(type_str)
base_type = scan_vars.strip_ptr_and_array(type_str)
fields = structs.get(base_type)
if not isinstance(fields, dict):
# Не структура и не массив — просто возвращаем пустой список
@@ -266,6 +318,12 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
else:
field_type_str = None
if '*' in field_type_str:
full_name_prefix = full_name + '*'
else:
full_name_prefix = full_name
# Обработка, если поле — строка (тип или массив)
if 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):
# Это одиночная структура — раскрываем рекурсивно
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 = {
'name': full_name,
'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'),
'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:
child['children'] = subchildren
children.append(child)
@@ -399,3 +457,163 @@ def expand_vars(vars_list, structs, typedefs):
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

498
Src/var_table.py Normal file
View File

@@ -0,0 +1,498 @@
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="Укажите размер короткого имени",
label_text="Количество символов:"):
super().__init__(parent)
self.setWindowTitle(title)
self.setFixedSize(320, 120) # Задаем фиксированный размер для аккуратного вида
# Основной вертикальный макет
main_layout = QVBoxLayout(self)
# Макет для ввода значения
input_layout = QHBoxLayout()
label = QLabel(label_text, 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, show_value_instead_of_shortname=0):
# Таблица переменных
if show_value_instead_of_shortname:
super().__init__(0, 8, parent)
self.setHorizontalHeaderLabels([
'',
'En',
'Name',
'Origin Type',
'Base Type',
'IQ Type',
'Return Type',
'Value'
])
self._show_value = True
else:
super().__init__(0, 8, parent)
self.setHorizontalHeaderLabels([
'',
'En',
'Name',
'Origin Type',
'Base Type',
'IQ Type',
'Return Type',
'Short Name'
])
self._show_value = False
self.setEditTriggers(QAbstractItemView.AllEditTriggers)
self.var_list = []
# QSettings
self.settings = QSettings("SET", "DebugVarEdit_VarTable")
shortsize = self.settings.value("shortname_size", True, type=int)
self._shortname_size = shortsize
if(self._show_value):
self._shortname_size = 3
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)]
self.iq_types = ['iq_none', 'iq', 'iq10', 'iq15', 'iq19', 'iq24']
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]
self.pt_types = [t.replace('pt_', '') for t in type_options]
self._iq_type_filter = list(self.iq_types)
self._pt_type_filter = list(self.pt_types)
self._ret_type_filter = list(self.iq_types)
header = self.horizontalHeader()
for col in range(self.columnCount()):
if col == self.columnCount() - 1:
header.setSectionResizeMode(col, QHeaderView.Stretch)
else:
header.setSectionResizeMode(col, QHeaderView.Interactive)
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
self.setUpdatesEnabled(False)
self.blockSignals(True)
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'])
name_edit.textChanged.connect(on_change_callback)
name_edit.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.name, name_edit)
# Origin Type
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.get('pt_type', 'unknown').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.get('iq_type', 'iq_none').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.get('return_type', 'iq_none').replace('t_', '')
if value not in self.iq_types:
ret_combo.addItem(value)
ret_combo.setCurrentText(value)
ret_combo.currentTextChanged.connect(on_change_callback)
ret_combo.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.ret_type, ret_combo)
# Последний столбец
if self._show_value:
if self._show_value:
val = var.get('value', '')
if val is None:
val = ''
else:
try:
f_val = float(val)
# Форматируем число с учетом self._shortname_size
if f_val.is_integer():
val = str(int(f_val))
else:
precision = getattr(self, "_shortname_size", 3) # по умолчанию 3
val = f"{f_val:.{precision}f}"
except ValueError:
# Если значение не число (строка и т.п.), оставляем как есть
val = str(val)
val_edit = QLineEdit(val)
val_edit.textChanged.connect(on_change_callback)
val_edit.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.short_name, val_edit)
else:
short_name_val = var.get('shortname', var['name'])
short_name_edit = QLineEdit(short_name_val)
short_name_edit.textChanged.connect(on_change_callback)
short_name_edit.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.short_name, short_name_edit)
self.blockSignals(False)
self.setUpdatesEnabled(True)
self.check()
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:
if not self._show_value:
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:
if self._show_value:
dlg = SetSizeDialog(self, title="Укажите точность", label_text="Кол-во знаков после запятой", initial_value=3)
else:
dlg = SetSizeDialog(self)
if dlg.exec_():
self._shortname_size = dlg.get_selected_size()
if not self._show_value:
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 not in allowed_items:
combo.addItem(current)
combo.setCurrentText(current)
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,389 +1,416 @@
#include "debug_tools.h"
#include "IQmathLib.h"
static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var);
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var);
#if !defined(GLOBAL_Q)
#define GLOBAL_Q 16
#endif
DebugLowLevel_t debug_ll = DEBUG_LOWLEVEL_INIT; ///< Ñòðóêòóðà îòëàäêè íèæíåãî óðîâíÿ (èíèöèàëèçàöèÿ)
long var_numb = 1;
long return_var;
long return_ll_var;
DebugVar_t dbg_var_ll;
int result;
char ext_date[] = {7, 233, 11, 07, 16, 50};
int Debug_Test_Example(void)
static int getDebugVar(DebugVar_t *var, int32_t *int_var, float *float_var);
static int convertDebugVarToIQx(DebugVar_t *var, int32_t *ret_var);
static int iqTypeToQ(DebugVarIQType_t t);
static int is_addr_in_allowed_ranges(uint32_t addr_val, const AddrRange_t *ranges, int count);
/**
* @brief Ìàññèâ äîïóñòèìûõ äèàïàçîíîâ àäðåñîâ äëÿ îòëàäî÷íîãî ÷òåíèÿ
*
* Âêëþ÷àåò â ñåáÿ íàáîð äèàïàçîíîâ ïàìÿòè, ðàçðåø¸ííûõ äëÿ äîñòóïà
* ôóíêöèåé Debug_LowLevel_ReadVar.
*/
static const AddrRange_t debug_allowed_ranges[] = ALLOWED_ADDRESS_RANGES;
/**
* @brief Êîëè÷åñòâî ýëåìåíòîâ â ìàññèâå debug_allowed_ranges
*/
static const int debug_allowed_ranges_count = sizeof(debug_allowed_ranges) / sizeof(debug_allowed_ranges[0]);
///////////////////////////----EXAPLE-----//////////////////////////////
int var_numb = 1; ///< Ïðèìåð ïåðåìåííîé äëÿ îòëàäêè
DebugVarName_t var_name; ///< Èìÿ ïåðåìåííîé
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);
return;
result = Debug_ReadVar(var_numb, &return_var);
result = Debug_ReadVarName(var_numb, var_name, 0);
if(Debug_LowLevel_Initialize(ext_date) == 0)
result = Debug_LowLevel_ReadVar(&dbg_var_ll, &return_ll_var);
if(Debug_LowLevel_Initialize(&ext_date) == 0)
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)
return 1;
int32_t tmp_var;
char *addr = var_ll->Ptr;
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; // àäðåñ âíå äîïóñòèìîãî äèàïàçîíà, èãíîðèðóåì
if(return_32b == NULL)
return DEBUG_ERR_INTERNAL;
if (var_ind >= DebugVar_Qnt)
return DEBUG_ERR_VAR_NUMB;
if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) ||
(dbg_vars[var_ind].ptr_type == pt_unknown))
return DEBUG_ERR_INVALID_VAR;
return convertDebugVarToIQx(&dbg_vars[var_ind], return_32b);
}
convertDebugVarToIQx(var_ll, return_long);
/**
* @brief ×èòàåò âîçâðàùàåìûé òèï (IQ) ïåðåìåííîé ïî èíäåêñó.
* @param var_ind èíäåêñ ïåðåìåííîé.
* @param vartype óêàçàòåëü äëÿ âîçâðàòà òèïà.
* @return int 0: óñïåõ, 1: îøèáêà.
* @details Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ âîçâðàùàåìîãî òèïà (IQ) ïåðåìåííûõ ïî èõ èíäåêñó.
*/
int Debug_ReadVarReturnType(int var_ind, int *vartype)
{
int rettype;
if(vartype == NULL)
return DEBUG_ERR_INTERNAL;
if (var_ind >= DebugVar_Qnt)
return DEBUG_ERR_VAR_NUMB;
if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) ||
(dbg_vars[var_ind].ptr_type == pt_unknown))
return DEBUG_ERR_INVALID_VAR;
*vartype = iqTypeToQ(dbg_vars[var_ind].return_type);
return 0;
}
int Debug_ReadVar(DebugVar_t *var, long *return_long)
/**
* @brief ×èòàåò òèï ïåðåìåííîé ïî èíäåêñó.
* @param var_ind èíäåêñ ïåðåìåííîé.
* @param vartype óêàçàòåëü äëÿ âîçâðàòà òèïà.
* @return int 0: óñïåõ, 1: îøèáêà.
* @details Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ òèïà ïåðåìåííûõ ïî èõ èíäåêñó.
*/
int Debug_ReadVarType(int var_ind, int *vartype)
{
long tmp_var;
if (var == NULL)
return 1;
if((var->ptr_type == pt_struct) || (var->ptr_type == pt_union) ||
(var->ptr_type == pt_unknown))
return 1;
int rettype;
if(vartype == NULL)
return DEBUG_ERR_INTERNAL;
if (var_ind >= DebugVar_Qnt)
return DEBUG_ERR_VAR_NUMB;
if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) ||
(dbg_vars[var_ind].ptr_type == pt_unknown))
return DEBUG_ERR_INVALID_VAR;
convertDebugVarToIQx(var, return_long);
*vartype = dbg_vars[var_ind].ptr_type;
return 0;
}
int Debug_ReadVarName(DebugVar_t *var, char *name_ptr)
switch(dbg_vars[var_ind].ptr_type)
{
if((var == NULL)||(name_ptr == NULL))
return 1;
int i;
// Êîïèðîâàíèå ñ çàùèòîé îò ïåðåïîëíåíèÿ è ÿâíîé îñòàíîâêîé ïî '\0'
for (i = 0; i < sizeof(var->name); i++)
{
name_ptr[i] = var->name[i];
if (var->name[i] == '\0')
case pt_int8:
case pt_int16:
case pt_int32:
case pt_float:
*vartype = dbg_vars[var_ind].ptr_type | DEBUG_SIGNED_VAR;
break;
default:
*vartype = dbg_vars[var_ind].ptr_type;
break;
}
// Ãàðàíòèðîâàííîå çàâåðøåíèå ñòðîêè (íà ñëó÷àé, åñëè â var->name íå áûëî '\0')
name_ptr[sizeof(var->name) - 1] = '\0';
return 0;
}
/**
* @brief ×èòàåò èìÿ ïåðåìåííîé ïî èíäåêñó.
* @param var_ind èíäåêñ ïåðåìåííîé.
* @param name_ptr óêàçàòåëü íà áóôåð èìåíè (DebugVarName_t).
* @return int 0: óñïåõ, 1: îøèáêà.
* @details Êîïèðóåò èìÿ ïåðåìåííîé â ïðåäîñòàâëåííûé áóôåð.
*/
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr, int *length)
{
int i;
if(name_ptr == NULL)
return DEBUG_ERR_INTERNAL;
int Debug_LowLevel_Initialize(const char* external_date)
if (var_ind >= DebugVar_Qnt)
return DEBUG_ERR_VAR_NUMB;
// Êîïèðîâàíèå ñ çàùèòîé îò ïåðåïîëíåíèÿ è ÿâíîé îñòàíîâêîé ïî '\0'
for (i = 0; i < sizeof(dbg_vars[var_ind].name); i++)
{
name_ptr[i] = dbg_vars[var_ind].name[i];
if (dbg_vars[var_ind].name[i] == '\0')
{
if(length != NULL)
*length = i;
break;
}
}
// Ãàðàíòèðîâàííîå çàâåðøåíèå ñòðîêè (íà ñëó÷àé, åñëè â var->name íå áûëî '\0')
name_ptr[sizeof(dbg_vars[var_ind].name) - 1] = '\0';
return 0;
}
/**
* @brief ×èòàåò çíà÷åíèå ïåðåìåííîé îòëàäêè ñ íèæíåãî óðîâíÿ.
* @param return_32b óêàçàòåëü íà ïåðåìåííóþ, êóäà çàïèñûâàåòñÿ ðåçóëüòàò.
* @return int 0: óñïåõ, 1: îøèáêà, 2: íåäîïóñòèìûé àäðåñ.
* @details Èñïîëüçóåò àäðåññ, ïåðåäàâàåìûé ñ òåðìèíàëêè äëÿ ïîëó÷åíèÿ çíà÷åíèÿ.
*/
int Debug_LowLevel_ReadVar(int32_t *return_32b)
{
uint8_t *addr = debug_ll.dbg_var.Ptr;
uint32_t addr_val = (uint32_t)addr;
if (return_32b == NULL)
return DEBUG_ERR_INTERNAL;
if (debug_ll.isVerified == 0)
return DEBUG_ERR_DATATIME;
if (is_addr_in_allowed_ranges(addr_val, debug_allowed_ranges, debug_allowed_ranges_count) != 0)
{
return DEBUG_ERR_ADDR; // Çàïðåù¸ííûé àäðåñ — íåëüçÿ ÷èòàòü
}
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) {
return -1;
return DEBUG_ERR_INTERNAL;
}
// Ïðåîáðàçóåì 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 &&
ext.month == build.month &&
ext.day == build.day &&
ext.hour == build.hour &&
ext.minute == build.minute)
if (external_date->year == debug_ll.build_date.year &&
external_date->month == debug_ll.build_date.month &&
external_date->day == debug_ll.build_date.day &&
external_date->hour == debug_ll.build_date.hour &&
external_date->minute == debug_ll.build_date.minute)
{
debug_ll.isVerified = 1;
return 0; // Ñîâïàëî
}
debug_ll.isVerified = 0;
return 1; // Íå ñîâïàëî
return DEBUG_ERR_DATATIME; // Íå ñîâïàëî
}
/**
* @brief ×èòàåò âîçâðàùàåìûé òèï (IQ) íèçêîóðîâíåíî çàäàííîé ïåðåìåííîé.
* @param var_ind èíäåêñ ïåðåìåííîé.
* @param vartype óêàçàòåëü äëÿ âîçâðàòà òèïà.
* @return int 0: óñïåõ, 1: îøèáêà.
* @details Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ âîçâðàùàåìîãî òèïà (IQ) ïåðåìåííûõ ïî èõ èíäåêñó.
*/
int Debug_LowLevel_ReadVarReturnType(int *vartype)
{
int rettype;
if(vartype == NULL)
return DEBUG_ERR_INTERNAL;
if((debug_ll.dbg_var.ptr_type == pt_struct) || (debug_ll.dbg_var.ptr_type == pt_union) ||
(debug_ll.dbg_var.ptr_type == pt_unknown))
return DEBUG_ERR_INVALID_VAR;
*vartype = iqTypeToQ(debug_ll.dbg_var.return_type);
return 0;
}
/**
* @brief ×èòàåò òèï íèçêîóðîâíåíî çàäàííîé ïåðåìåííîé.
* @param var_ind èíäåêñ ïåðåìåííîé.
* @param vartype óêàçàòåëü äëÿ âîçâðàòà òèïà.
* @return int 0: óñïåõ, 1: îøèáêà.
*/
int Debug_LowLevel_ReadVarType(int *vartype)
{
int rettype;
if(vartype == NULL)
return DEBUG_ERR_INTERNAL;
if((debug_ll.dbg_var.ptr_type == pt_struct) || (debug_ll.dbg_var.ptr_type == pt_union) ||
(debug_ll.dbg_var.ptr_type == pt_unknown))
return DEBUG_ERR_INVALID_VAR;
*vartype = debug_ll.dbg_var.ptr_type;
switch(debug_ll.dbg_var.ptr_type)
{
case pt_int8:
case pt_int16:
case pt_int32:
case pt_float:
*vartype = debug_ll.dbg_var.ptr_type | DEBUG_SIGNED_VAR;
break;
default:
*vartype = debug_ll.dbg_var.ptr_type;
break;
}
return 0;
}
/////////////////////----INTERNAL FUNCTIONS-----////////////////////////
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 0; // îøèáêà
}
/**
* @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;
int64_t iq_united64 = 0;
int64_t iq_final64 = 0;
int status;
float float_numb;
if(getDebugVar(var, &iq_numb, &float_numb) != 0)
return 1;
status = getDebugVar(var, &iq_numb, &float_numb);
if(status != 0)
return status;
// ïðèâåäåíèå ê îäíîìó IQ
switch(var->iq_type)
{
case t_iq_none:
if(var->ptr_type == pt_float)
{
iq_united = _IQ(float_numb);
int src_q = iqTypeToQ(var->iq_type);
int dst_q = iqTypeToQ(var->return_type);
// Êîíâåðòàöèÿ ê GLOBAL_Q (64-áèò)
if (var->iq_type == t_iq_none) {
if (var->ptr_type == pt_float) {
// float_numb óìíîæàåì íà 2^GLOBAL_Q
// Ðåçóëüòàò ïðèâîäèì ê 64 áèòà
iq_united64 = (int64_t)(float_numb * ((uint32_t)1 << GLOBAL_Q));
} else {
iq_united64 = ((int64_t)iq_numb) << GLOBAL_Q;
}
} else {
int shift = GLOBAL_Q - src_q;
if (shift >= 0)
iq_united64 = ((int64_t)iq_numb) << shift;
else
{
iq_united = _IQ(iq_numb);
}
break;
case t_iq1:
iq_united = _IQ1toIQ(iq_numb);
break;
case t_iq2:
iq_united = _IQ2toIQ(iq_numb);
break;
case t_iq3:
iq_united = _IQ3toIQ(iq_numb);
break;
case t_iq4:
iq_united = _IQ4toIQ(iq_numb);
break;
case t_iq5:
iq_united = _IQ5toIQ(iq_numb);
break;
case t_iq6:
iq_united = _IQ6toIQ(iq_numb);
break;
case t_iq7:
iq_united = _IQ7toIQ(iq_numb);
break;
case t_iq8:
iq_united = _IQ8toIQ(iq_numb);
break;
case t_iq9:
iq_united = _IQ9toIQ(iq_numb);
break;
case t_iq10:
iq_united = _IQ10toIQ(iq_numb);
break;
case t_iq11:
iq_united = _IQ11toIQ(iq_numb);
break;
case t_iq12:
iq_united = _IQ12toIQ(iq_numb);
break;
case t_iq13:
iq_united = _IQ13toIQ(iq_numb);
break;
case t_iq14:
iq_united = _IQ14toIQ(iq_numb);
break;
case t_iq15:
iq_united = _IQ15toIQ(iq_numb);
break;
case t_iq16:
iq_united = _IQ16toIQ(iq_numb);
break;
case t_iq17:
iq_united = _IQ17toIQ(iq_numb);
break;
case t_iq18:
iq_united = _IQ18toIQ(iq_numb);
break;
case t_iq19:
iq_united = _IQ19toIQ(iq_numb);
break;
case t_iq20:
iq_united = _IQ20toIQ(iq_numb);
break;
case t_iq21:
iq_united = _IQ21toIQ(iq_numb);
break;
case t_iq22:
iq_united = _IQ22toIQ(iq_numb);
break;
case t_iq23:
iq_united = _IQ23toIQ(iq_numb);
break;
case t_iq24:
iq_united = _IQ24toIQ(iq_numb);
break;
case t_iq25:
iq_united = _IQ25toIQ(iq_numb);
break;
case t_iq26:
iq_united = _IQ26toIQ(iq_numb);
break;
case t_iq27:
iq_united = _IQ27toIQ(iq_numb);
break;
case t_iq28:
iq_united = _IQ28toIQ(iq_numb);
break;
case t_iq29:
iq_united = _IQ29toIQ(iq_numb);
break;
case t_iq30:
iq_united = _IQ30toIQ(iq_numb);
break;
iq_united64 = ((int64_t)iq_numb) >> (-shift);
}
// ïðèâåäåíèå îáùåãî IQ ê çàïðàøèâàåìîìó
switch(var->return_type)
{
case t_iq_none:
iq_final = (long)_IQtoF(iq_united);
break;
case t_iq1:
iq_final = _IQtoIQ1(iq_united);
break;
case t_iq2:
iq_final = _IQtoIQ2(iq_united);
break;
case t_iq3:
iq_final = _IQtoIQ3(iq_united);
break;
case t_iq4:
iq_final = _IQtoIQ4(iq_united);
break;
case t_iq5:
iq_final = _IQtoIQ5(iq_united);
break;
case t_iq6:
iq_final = _IQtoIQ6(iq_united);
break;
case t_iq7:
iq_final = _IQtoIQ7(iq_united);
break;
case t_iq8:
iq_final = _IQtoIQ8(iq_united);
break;
case t_iq9:
iq_final = _IQtoIQ9(iq_united);
break;
case t_iq10:
iq_final = _IQtoIQ10(iq_united);
break;
case t_iq11:
iq_final = _IQtoIQ11(iq_united);
break;
case t_iq12:
iq_final = _IQtoIQ12(iq_united);
break;
case t_iq13:
iq_final = _IQtoIQ13(iq_united);
break;
case t_iq14:
iq_final = _IQtoIQ14(iq_united);
break;
case t_iq15:
iq_final = _IQtoIQ15(iq_united);
break;
case t_iq16:
iq_final = _IQtoIQ16(iq_united);
break;
case t_iq17:
iq_final = _IQtoIQ17(iq_united);
break;
case t_iq18:
iq_final = _IQtoIQ18(iq_united);
break;
case t_iq19:
iq_final = _IQtoIQ19(iq_united);
break;
case t_iq20:
iq_final = _IQtoIQ20(iq_united);
break;
case t_iq21:
iq_final = _IQtoIQ21(iq_united);
break;
case t_iq22:
iq_final = _IQtoIQ22(iq_united);
break;
case t_iq23:
iq_final = _IQtoIQ23(iq_united);
break;
case t_iq24:
iq_final = _IQtoIQ24(iq_united);
break;
case t_iq25:
iq_final = _IQtoIQ25(iq_united);
break;
case t_iq26:
iq_final = _IQtoIQ26(iq_united);
break;
case t_iq27:
iq_final = _IQtoIQ27(iq_united);
break;
case t_iq28:
iq_final = _IQtoIQ28(iq_united);
break;
case t_iq29:
iq_final = _IQtoIQ29(iq_united);
break;
case t_iq30:
iq_final = _IQtoIQ30(iq_united);
break;
// Êîíâåðòàöèÿ èç GLOBAL_Q â öåëåâîé IQ (64-áèò)
if (var->return_type == t_iq_none) {
// Âîçâðàùàåì öåëîå, îòáðîñèâ äðîáíóþ ÷àñòü
*ret_var = (uint32_t)(iq_united64 >> GLOBAL_Q);
} else {
int shift = dst_q - GLOBAL_Q;
if (shift >= 0)
iq_final64 = iq_united64 << shift;
else
iq_final64 = iq_united64 >> (-shift);
*ret_var = (int32_t)iq_final64;
}
*ret_var = iq_final;
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)
return 1; // îøèáêà: null óêàçàòåëü
uint8_t *addr = var->Ptr;
uint32_t addr_val = (uint32_t)addr;
char *addr = var->Ptr;
unsigned long addr_val = (unsigned long)addr;
if (!var || !int_var || !float_var || !var->Ptr)
return DEBUG_ERR_INTERNAL; // îøèáêà: null óêàçàòåëü
switch (var->ptr_type)
{
case pt_int8: // 8 áèò
if ((addr_val & ALIGN_8BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile int8_t *)addr);
break;
case pt_uint8:
// âûðàâíèâàíèå íå íóæíî äëÿ 8 áèò
*int_var = *((volatile char *)addr);
if ((addr_val & ALIGN_8BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile uint8_t *)addr);
break;
case pt_int16: // 16 áèò (int)
if ((addr_val & ALIGN_16BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile int16_t *)addr);
break;
case pt_uint16:
if (addr_val & 0x1) // ïðîâåðêà âûðàâíèâàíèÿ ïî 2 áàéòàì
return 2; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile int *)addr);
if ((addr_val & ALIGN_16BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile uint16_t *)addr);
break;
case pt_int32: // 32 áèò (long)
case pt_int32: // 32 áèò
if ((addr_val & ALIGN_32BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile int32_t *)addr);
break;
case pt_uint32:
if (addr_val & 0x3) // ïðîâåðêà âûðàâíèâàíèÿ ïî 4 áàéòàì
return 3; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile long *)addr);
if ((addr_val & ALIGN_32BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile uint32_t *)addr);
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 áàéòà)
if (addr_val & 0x3) // ïðîâåðêà âûðàâíèâàíèÿ ïî 4 áàéòàì
return 4; // îøèáêà âûðàâíèâàíèÿ
if ((addr_val & ALIGN_FLOAT) != 0) // ïðîâåðêà âûðàâíèâàíèÿ
return DEBUG_ERR_ADDR_ALIGN; // îøèáêà âûðàâíèâàíèÿ
*float_var = *((volatile float *)addr);
break;
default:
return 1; // íåïîääåðæèâàåìûé òèï
return DEBUG_ERR_INVALID_VAR; // íåïîääåðæèâàåìûé òèï
// äëÿ óêàçàòåëåé è ìàññèâîâ íå ïîääåðæèâàåòñÿ ÷òåíèå
// case pt_ptr_int8:
// case pt_ptr_int16:
@@ -401,3 +428,24 @@ static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var)
return 0; // óñïåõ
}
/**
* @brief Ïðîâåðÿåò, âõîäèò ëè àäðåñ â îäèí èç äîïóñòèìûõ äèàïàçîíîâ
*
* @param addr_val - Çíà÷åíèå àäðåñà äëÿ ïðîâåðêè
* @param ranges - Óêàçàòåëü íà ìàññèâ äèàïàçîíîâ AddrRange_t
* @param count - Êîëè÷åñòâî äèàïàçîíîâ â ìàññèâå
* @return 0 åñëè àäðåñ íàõîäèòñÿ â îäíîì èç äèàïàçîíîâ, èíà÷å 1
*/
static int is_addr_in_allowed_ranges(uint32_t addr_val, const AddrRange_t *ranges, int count)
{
int i;
for (i = 0; i < count; i++) {
if (addr_val >= ranges[i].start && addr_val <= ranges[i].end) {
return 0;
}
}
return 1;
}

View File

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

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" }, \
};

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

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

BIN
parse_xml/parse_xml.exe Normal file

Binary file not shown.

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