Files
Andrey 05be38a04a Обновить Flash/CAN, версии сборок и управление периферией
Добавить защищённое обновление приложения через RS и CAN с сохранением
загрузчика, проверкой CRC, восстановлением передачи и защитой EEPROM.
Подключить общий процесс сборки C2000 и автоматический счётчик версии
для согласованных RAM, Flash, CAN и паспортов бинарных образов.

Уточнить запуск ЦАП, обработку измерений и параметров нагрузки,
начальную индикацию и документацию регистров GAS.
Дополнить модели аппаратуры и изолировать заголовки CAN в тестах.

Проверка: 26 тестов выпуска, 4 теста восстановления CAN и восемь
программных моделей CAN, Flash, EEPROM, ЦАП, параметров и TESTLAMP.
2026-11-06 16:11:11 +03:00

81 lines
9.8 KiB
Python

# Проверка load_control на модели пк. Комментарии и исходный текст в UTF-8.
# Продолжает текущий вызов, список или выражение: """Compile the production DAC.c against GPIO/IRQ mocks; no hardware writes."""; порядок аргументов сохраняется.
"""Compile the production DAC.c against GPIO/IRQ mocks; no hardware writes."""
# Подключает модуль os для этой проверки или подготовки сборки.
import os
import argparse # Позволяет независимо запускать регрессию EEPROM без аппаратных заглушек полного автомата нагрузки.
# Разбирает только границы настоящих функций message.c, сохраняя их неизменённое тело в регрессии параметров.
import re
# Импортирует необходимые имена: from pathlib import Path.
from pathlib import Path
# Подключает модуль shutil для этой проверки или подготовки сборки.
import shutil
# Подключает модуль subprocess для этой проверки или подготовки сборки.
import subprocess
# Подключает модуль tempfile для этой проверки или подготовки сборки.
import tempfile
parser = argparse.ArgumentParser(description=__doc__) # Описывает существующую полную проверку и отдельный независимый сценарий применения параметров.
parser.add_argument('--parameters-only', action='store_true', help='Проверить только Load/Default параметров и стартовую индикацию задатчика') # Ограничивает запуск функциями message.c, не подключая аппаратный DAC.c.
arguments = parser.parse_args() # Проверяет режим до компиляции временных моделей.
# Задаёт root по выражению справа; значение используется последующими операциями.
root = Path(__file__).resolve().parents[2]
# Задаёт compiler по выражению справа; значение используется последующими операциями.
compiler = os.environ.get('CC') or shutil.which('gcc') or 'C:/msys64/mingw64/bin/gcc.exe'
# Задаёт os.environ['PATH'] по выражению справа; значение используется последующими операциями.
os.environ['PATH'] = str(Path(compiler).resolve().parent) + os.pathsep + os.environ['PATH']
# Открывает управляемый ресурс tempfile.TemporaryDirectory(prefix='load-control-') as tmp с автоматическим освобождением.
with tempfile.TemporaryDirectory(prefix='load-control-') as tmp:
if not arguments.parameters_only: # Полный автомат проверяется по умолчанию; его аппаратные зависимости не блокируют независимый запуск EEPROM-регрессии.
# Задаёт exe по выражению справа; значение используется последующими операциями.
exe = Path(tmp) / 'test.exe'
flash = (root / 'Source/Internal/flash_rs.c').read_text(encoding='utf-8') # Проверяет настоящий запрет активной нагрузки перед переходом к Flash API.
guard = re.search(r'^static unsigned int flash_load_guard\([^\n]*\n\{[^\n]*\n[^\n]*\n\}', flash, re.M) # Извлекает короткий production-helper без переписывания его условия в тестовой модели.
assert guard, 'Production Flash load guard missing' # Переименование или удаление защиты не должно незаметно отключить тест перед erase.
(Path(tmp) / 'production_flash_guard.inc').write_text(guard.group() + '\n', encoding='utf-8') # Включает реальный helper в тот же C-модуль, который исполняет production DAC.c.
# Запускает внешний компилятор, генератор или тест; ненулевой результат проверяется параметрами вызова.
subprocess.run([compiler, '-Wall', '-Wextra', '-std=c99',
# Продолжает текущий вызов, список или выражение: '-Itests/load_control/stubs', '-ISource/Internal/Include',; порядок аргументов сохраняется.
'-Itests/load_control/stubs', '-ISource/Internal/Include', '-I' + tmp, # Добавляет только каталог извлечённого рабочего Flash-helper этой проверки.
# Задаёт 'tests/load_control/load_control_test.c', '-o', str(exe)], cwd по выражению справа; значение используется последующими операциями.
'tests/load_control/load_control_test.c', '-o', str(exe)], cwd=root, check=True)
# Запускает внешний компилятор, генератор или тест; ненулевой результат проверяется параметрами вызова.
subprocess.run([str(exe)], check=True, timeout=10)
# Читает рабочие загрузку и заводской сброс параметров для отдельной проверки живых START/RESET.
message = (root / 'Source/Internal/message.c').read_text(encoding='utf-8')
# Маскирует комментарии пробелами той же длины, чтобы фигурные скобки пояснений не влияли на поиск границ функций.
masked = re.sub(r'/\*.*?\*/|//[^\n]*', lambda match: ' ' * len(match.group()), message, flags=re.S)
# Собирает только проверяемые рабочие функции: EEPROM и GPIO заменяются моделью, сам алгоритм загрузки остаётся исходным.
functions = []
# Извлекает helper и обе ветки применения параметров независимо от форматирования вложенных блоков в исходнике.
for name in ('reset_parameter_commands', 'Default_params', 'Load_params'):
# Находит объявление функции с настоящим именем и необязательным static.
match = re.search(r'^(?:static )?void ' + name + r'\(', masked, re.M)
# Не допускает молчаливого запуска модели после удаления или переименования рабочего обработчика.
assert match, name
# Начинает подсчёт вложенных блоков с первой открывающей скобки тела найденной функции.
end, depth = masked.index('{', match.start()), 0
# Идёт до парной закрывающей скобки, сохраняя все вложенные условия и циклы рабочего кода.
while end < len(masked):
# Учитывает открывающую скобку только после удаления комментариев.
depth += masked[end] == '{'
# Учитывает соответствующую закрывающую скобку в том же уровне вложенности.
depth -= masked[end] == '}'
# Переходит к следующему символу, включая последнюю закрывающую скобку в извлекаемый текст.
end += 1
# Нулевой уровень означает завершение всей функции, а не первого вложенного if.
if depth == 0:
# Завершает поиск, чтобы не включить соседний обработчик Modbus.
break
# Копирует исходный текст функции вместе с русскими комментариями без изменения условий и присваиваний.
functions.append(message[match.start():end])
# Передаёт извлечённый production-код компилятору из временного каталога, не записывая generated-файлы в репозиторий.
(Path(tmp) / 'production_parameters.inc').write_text('\n'.join(functions), encoding='utf-8')
# Выбирает отдельный исполняемый файл, чтобы проверка параметров не подменяла прежние регрессии автомата DAC.
parameters_exe = Path(tmp) / 'parameters.exe'
# Компилирует реальную загрузку параметров с контролем IRQ, модели EEPROM и содержимого R127.
subprocess.run([compiler, '-Wall', '-Wextra', '-std=c99', '-I' + tmp, 'tests/load_control/parameter_commands_test.c', '-o', str(parameters_exe)], cwd=root, check=True)
# Выполняет комбинации живых START/RESET, сохранённых команд, неверной CRC, холодного старта и CAN во время чтения.
subprocess.run([str(parameters_exe)], check=True, timeout=10)