Files
Balsam_167/UKSSTMS320F28335/build_identity.py
Andrey fe95dbff3d Перенести паспорт и проверку прошивки из 167 в 166
Добавить неизменяемое окно firmware-info v1 0x1200 из 12 слов для FC03 и служебного CAN, запретить запись. Сохранить текущую технологическую карту и логгер v4 с записью 12 слов.

Подключить общий генератор версии 1.0.0 для Balsam-166-PM35, prebuild CCS и локальный выпуск BIN с manifest и SHA-256. Проверять соответствие встроенных слов манифесту до публикации; старый OUT без паспорта отклонять.

Проверка: реальные обработчики RS/CAN и CRC под GCC O3, прежний тест логгера, анализатор установленного SetGUI, полная сборка TI C2000 22.6.0.LTS. BIN 30248 байт при границе параметров 32508 байт. Аппаратная проверка требует установки нового образа и перезапуска платы.
2026-10-16 12:05:44 +03:00

68 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Выпускает BIN 166 с проверяемым паспортом и SHA-256 по той же схеме, что 167.""" # Генерация версии выполняется отдельно до компиляции.
import argparse # Разбирает пути текущей конфигурации CCS и компилятора TI.
from datetime import datetime # Преобразует время UTC паспорта в те же шесть компонентов, что firmware-info v1.
import json # Читает именно паспорт завершившейся компиляции.
from pathlib import Path # Привязывает настройки и результаты к текущему проекту.
import shutil # Находит компилятор и копирует только полностью проверенный выпуск.
import struct # Проверяет 16-битные слова загрузочного SCI8 без исполнения кода прошивки.
import sys # Подключает общий postbuild вместо копии конвертера из 167.
import tempfile # Не публикует результаты конверсии, пока встроенный паспорт не прошёл проверку.
ROOT = Path(__file__).resolve().parents[1] # Корень 166 содержит существующие Source и Bin.
PROJECT = Path(__file__).resolve().parent # Конфигурации Debug/Release и заголовок версии находятся рядом со сценарием.
TEMPLATES = ROOT.parent / "newProject/templates" # Использует ту же общую библиотеку firmware-info, что проект 167.
def verify_embedded(image, metadata): # Не позволяет выдать новый манифест за паспорт старого OUT без встроенной версии.
data = image.read_bytes() # Проверяет точные октеты, которые будут отправлены в плату.
if data[:2] != b"\xaa\x08" or not 24 <= len(data) <= 32508: # SCI8 имеет ключ 0x08AA; граница параметров 166 равна (0x3FFF - 129) * 2 = 32508 байт.
raise ValueError("SCI8 is invalid or overlaps the 166 parameter area at byte 32508") # Не допускает повреждения существующих параметров EEPROM.
stamp = datetime.fromisoformat(metadata["built_at_utc"]) # Использует исходное время prebuild без подмены временем конверсии BIN.
build_id = metadata["build_id"] # Ровно восемь ASCII-символов совпадают с четырьмя регистрами firmware-info v1.
words = [1, *metadata["version"], stamp.year, stamp.month * 256 + stamp.day, stamp.hour * 256 + stamp.minute, stamp.second] # Восстанавливает первые восемь регистров независимо от C-упаковщика.
words += [ord(build_id[i]) * 256 + ord(build_id[i + 1]) for i in range(0, 8, 2)] # Восстанавливает четыре слова Build ID в сетевом порядке символов.
expected = struct.pack("<12H", *words) # Загрузочная таблица SCI8 хранит 16-битные слова C28x младшим октетом вперёд.
offset, found = 22, 0 # Первые 22 октета заняты ключом, настройками и аппаратной точкой входа.
while offset + 2 <= len(data): # Каждый шаг потребляет заголовок либо завершает конечный файл; ожидания аппаратуры нет.
count, = struct.unpack_from("<H", data, offset) # Размер очередной секции задан в 16-битных словах C28x.
offset += 2 # Переходит к адресу секции либо концу таблицы при нулевом размере.
if not count: break # Нулевая секция завершает SCI8 и не содержит адреса назначения.
end = offset + 4 + count * 2 # Четыре октета адреса не входят в проверяемую полезную нагрузку секции.
if end > len(data): raise ValueError("Truncated SCI8 section") # Останавливает выпуск при выходе секции за фактический размер файла.
payload = data[offset + 4:end] # Рассматривает загружаемые слова, а не случайное совпадение в заголовке.
found += sum(payload[i:i + 24] == expected for i in range(0, len(payload) - 23, 2)) # Паспорт должен присутствовать один раз на границе слова C28x.
offset = end # Переходит к следующей непересекающейся секции таблицы.
if offset != len(data) or count or found != 1: # Запрещает хвосты, отсутствующий терминатор и манифест от другой компиляции.
raise ValueError(f"SCI8 does not contain exactly one matching prebuild identity: {found}") # Требует полноценную пересборку вместо фиктивного подписания старого BIN.
def find_compiler(): # Сохраняет возможность ручного запуска build_bin.bat без жёсткой привязки к одной версии CCS.
executable = shutil.which("cl2000") or shutil.which("cl2000.exe") # Предпочитает компилятор среды текущего проекта, а не старый hex2000 рядом с BIN.
if executable: return Path(executable).resolve().parents[1] # Возвращает корень toolchain с каталогом bin/hex2000.exe.
installed = sorted(Path("C:/ti").glob("ccs*/ccs/tools/compiler/ti-cgt-c2000_*")) # Для установленного CCS использует только штатные каталоги TI.
installed = [path for path in installed if (path / "bin/hex2000.exe").is_file()] # Не принимает неполную установку за пригодный конвертер.
if not installed: raise FileNotFoundError("Pass --compiler with the C2000 toolchain root") # Явно останавливает ручной запуск без инструментов.
return installed[-1] # Выбирает последнюю найденную установленную цепочку только при отсутствии заданной CCS версии.
def build(compiler, build_dir, artifact): # Выполняет общий postbuild и локальную публикацию после проверки встроенной версии.
sys.path.insert(0, str(TEMPLATES / "c/firmware-info/tools")) # Открывает общие identity.py и c2000_postbuild.py из той же зависимости, что 167.
from c2000_postbuild import postbuild # Не копирует формат манифеста, архивирование и вычисление SHA-256.
metadata_path = build_dir / "generated/firmware_identity.json" # Отсутствующий prebuild должен остановить выпуск вместо использования произвольной версии.
metadata = json.loads(metadata_path.read_text(encoding="utf-8")) # Читает JSON, по которому компилятор получил firmware_identity.h.
if metadata.get("product") != "Balsam-166-PM35" or metadata.get("target") != "TMS320F28335": raise ValueError("Run the 166 prebuild before publishing this image") # Старый generated от ветки 167 не должен выдавать её product за паспорт 166.
output = build_dir / "firmware" # Сохраняет прежнюю для 167 структуру локального каталога выпусков.
with tempfile.TemporaryDirectory(prefix=".identity-release-", dir=build_dir) as directory: # Непроверенные BIN и manifest существуют только во временном каталоге текущего вызова.
stage = Path(directory); staged_image = postbuild(compiler, build_dir, artifact, metadata_path, stage) # Общий код создаёт HEX, BIN, SHA-256 и архив, пока не доступные как готовый выпуск.
verify_embedded(staged_image, metadata) # Не переносит в firmware или Bin результаты от старого OUT либо другой компиляции.
output.mkdir(parents=True, exist_ok=True) # Создаёт каталог публикации только после успешной проверки.
for item in stage.iterdir(): # Сохраняет только результаты этого вызова, не сканируя старые выпуски.
if item.is_dir(): shutil.copytree(item, output / item.name, dirs_exist_ok=True) # Публикует полный архив проверенной сборки.
else: shutil.copy2(item, output / item.name) # Переносит плоские HEX/BIN и контрольные файлы без подмены других исходников.
image = output / staged_image.name # После удаления временного каталога возвращает постоянный путь проверенного BIN.
for file in (image, image.with_suffix(".bin.manifest.json"), image.with_suffix(".bin.sha256"), image.parent / (image.stem.rsplit("_", 1)[0] + ".hex")): # Сохраняет рядом проверенный BIN, его контрольные файлы и прежний Intel HEX для CCS.
shutil.copy2(file, ROOT / "Bin" / file.name) # Основной каталог получает только результат успешной текущей сборки.
shutil.copy2(image.parent / (image.stem.rsplit("_", 1)[0] + ".hex.map"), ROOT / "Bin/UKSSTMS320F28335.map") # Согласовывает прежнюю основную карту конверсии с новым HEX и OUT.
print(ROOT / "Bin" / image.name) # Показывает конкретный BIN, который нужно выбрать в SetGUI вместе с лежащим рядом манифестом.
return image # Позволяет тестам проверить результат без поиска случайного старого файла.
if __name__ == "__main__": # Импорт проверки SCI8 не выполняет сборку или публикацию файлов.
parser = argparse.ArgumentParser(description=__doc__) # Описывает контракт запуска из CCS и build_bin.bat.
parser.add_argument("--compiler", type=Path) # CCS передаёт CG_TOOL_ROOT; ручной вызов может использовать обнаружение установленного компилятора.
parser.add_argument("--build-dir", type=Path, default=PROJECT / "Debug") # Изолирует паспорт и архив выбранной конфигурации.
parser.add_argument("--artifact", default=str(ROOT / "Bin/UKSSTMS320F28335")) # Общий postbuild ожидает полный путь OUT без расширения.
args = parser.parse_args() # Проверяет параметры до выполнения конверсии.
build((args.compiler or find_compiler()).resolve(), args.build_dir.resolve(), args.artifact) # Публикует только образ, соответствующий своему встроенному паспорту.