Принимайте INITLOAD, LOAD и TFLASH общим автоматом с повторной проверкой SCI8/FAB1 до стирания и сохранением сектора A. Сохраняйте параметры прошивки в паспорте prebuild, выпускайте независимые образы загрузчика и приложения с manifest и SHA-256. Проверено: шесть испытаний модели, включая обрывы записи и восстановление.
51 lines
9.6 KiB
Python
51 lines
9.6 KiB
Python
"""Настройки формы прошивки из JSON проекта и manifest без зависимости от Qt.""" # Один контракт обслуживает сборщики Keil/CCS и потребителей комплекта.
|
||
from copy import deepcopy # Возвращает отдельный профиль, чтобы GUI не изменял сохранённый паспорт.
|
||
|
||
LOADER_TRANSPORTS = {"manual": "manual", "set-rs485": "rs485", "set-can": "can", "protocan-can": "can", "stm32-uart": "stm32", "stm32-usb": "stm32", "f28335-jtag": "tms", "f28335-eeprom": "tms", "f28335-flash-rs": "tms"} # Коды совпадают с действующими загрузчиками SETGUI; транспорт сам по себе не определяет протокол.
|
||
COMMON_MODES = {"set-rs485": 0, "set-can": 1, "stm32-uart": 2, "protocan-can": 6, "stm32-usb": 7} # Индексы существующей общей формы нужны только порту GUI, без кодирования пакетов.
|
||
CAN_FIELDS = {"bitrate", "node_id", "product_type", "hardware_min", "hardware_max", "base_address"} # Скорость CAN выражается в бит/с, адрес приложения — в байтах.
|
||
RS_FIELDS = {"baudrate", "controller", "timeout_ms"} # Профиль ПМ35 задаёт скорость UART в бодах, адрес контроллера и добавочный тайм-аут ответа в миллисекундах.
|
||
PROFILE_FIELDS = {"manual": set(), "set-rs485": {"baudrate", "base_address"}, "set-can": CAN_FIELDS, "protocan-can": (CAN_FIELDS - {"base_address"}) | {"device_type", "session_id", "memory"}, "stm32-uart": {"baudrate", "base_address"}, "stm32-usb": {"base_address"}, "f28335-jtag": set(), "f28335-eeprom": RS_FIELDS, "f28335-flash-rs": RS_FIELDS} # ProtoCAN получает адреса из самого образа; остальные поля соответствуют действующим контролам загрузчиков.
|
||
INTEGER_LIMITS = {"bitrate": (1, 1000000), "baudrate": (1, 2147483647), "node_id": (0, 255), "product_type": (0, 65535), "hardware_min": (0, 255), "hardware_max": (0, 255), "base_address": (0, 4294967295), "controller": (0, 255), "timeout_ms": (50, 10000), "device_type": (0, 7), "session_id": (1, 255)} # Пределы взяты из полей SETGUI: Classic CAN до 1 Мбит/с, uint32 адреса и диапазоны существующих виджетов.
|
||
|
||
|
||
LOADER_TRANSPORTS["f28335-sci"] = "tms" # Штатный первичный SCI-загрузчик UlitCo использует собственный маршрут, отдельный от BIOS ПМ35.
|
||
COMMON_MODES["f28335-sci"] = 9 # Новый режим добавлен после существующих USB и STM32/J-Link без изменения их индексов.
|
||
PROFILE_FIELDS["f28335-sci"] = {"baudrate", "oscillator_hz", "enter_monitor", "timeout_ms"} # Частота кварца нужна Flash API, а переход Monitor передаётся только по явной настройке.
|
||
|
||
|
||
def validate_flashing(value): # Проверяет весь блок до изменения версии, формы или сохранённого manifest.
|
||
if not isinstance(value, dict) or set(value) - {"default_loader", "profiles"}: # Опечатки и null не должны молча превращаться в штатную скорость.
|
||
raise ValueError("flashing: ожидается объект с default_loader и profiles") # Сообщает имя исправляемого раздела проектного JSON.
|
||
profiles = value.get("profiles", {}) # Отсутствующий раздел сохраняет совместимость старых проектов без настроек прошивки.
|
||
if not isinstance(profiles, dict): # Список профилей должен сопоставлять стабильный код загрузчика с его параметрами.
|
||
raise ValueError("flashing.profiles: ожидается объект") # Не допускает строку или массив вместо именованных профилей.
|
||
for loader, profile in profiles.items(): # Проверяет также альтернативные CAN/RS-профили до резервирования номера сборки.
|
||
if loader not in PROFILE_FIELDS or not isinstance(profile, dict): # Неизвестный протокол требует поддержки в GUI, а не угадывания по названию.
|
||
raise ValueError("flashing.profiles: неизвестный загрузчик или неверный профиль " + str(loader)) # Указывает конкретный неподдержанный ключ.
|
||
unknown = set(profile) - PROFILE_FIELDS[loader] # Исключает параметры другого протокола и неработающие настройки интерфейса.
|
||
if unknown: # Лишнее поле часто означает опечатку в скорости или неверный выбор загрузчика.
|
||
raise ValueError("flashing." + loader + ": неподдерживаемые поля " + ", ".join(sorted(unknown))) # Не создаёт manifest с настройками, которые GUI не способен применить.
|
||
for field, number in profile.items(): # Проверяет типы без неявного преобразования строк и логических значений в числа.
|
||
if field in INTEGER_LIMITS: # Все числовые параметры имеют явно определённые единицы и пределы.
|
||
low, high = INTEGER_LIMITS[field] # Диапазон соответствует конкретному виджету или целочисленному адресу загрузчика.
|
||
if loader == "protocan-can" and field == "node_id": high = 15 # Поле Device действующей формы ProtoCAN ограничено четырьмя битами.
|
||
if type(number) is not int or not low <= number <= high: # JSON true и дробные скорости запрещены даже при числовой совместимости Python.
|
||
raise ValueError(f"flashing.{loader}.{field}: нужно целое число {low}..{high}") # Останавливает prebuild и импорт до применения неверного значения.
|
||
elif field == "enter_monitor" and type(number) is not bool: # Переход UlitCo в первичный загрузчик включается явным логическим значением JSON.
|
||
raise ValueError("flashing.f28335-sci.enter_monitor: нужно true или false") # Не трактует строку false как разрешение управляющей команды.
|
||
elif field == "oscillator_hz" and (type(number) is not int or number not in (20000000, 25000000, 30000000)): # Целочисленные частоты кварца дают 100, 125 и 150 МГц при штатном PLL ядра TI.
|
||
raise ValueError("flashing.f28335-sci.oscillator_hz: нужно 20000000, 25000000 или 30000000") # Ошибочная частота не должна приводить к неверным временам Flash API.
|
||
elif field == "memory" and number not in ("eeprom", "flash", "stm32"): # Коды соответствуют существующему выбору памяти ProtoCAN.
|
||
raise ValueError("flashing.protocan-can.memory: нужно eeprom, flash или stm32") # Не подменяет назначение неподдержанного значения EEPROM по умолчанию.
|
||
if profile.get("hardware_min", 0) > profile.get("hardware_max", 255): # Проверяет согласованность диапазона аппаратных ревизий до изменения формы.
|
||
raise ValueError("flashing." + loader + ": hardware_min больше hardware_max") # Неверный диапазон совместимости не должен попасть в опубликованный паспорт.
|
||
default = value.get("default_loader") # Отсутствующий маршрут оставляет прежнее определение назначения по артефакту.
|
||
if "default_loader" in value and (not isinstance(default, str) or default not in profiles): # Явный маршрут обязан иметь собственный проверенный профиль, включая пустой manual/JTAG.
|
||
raise ValueError("flashing.default_loader: укажите загрузчик из profiles") # Ссылка на отсутствующий профиль не должна оставлять параметры предыдущего изделия.
|
||
return deepcopy(value) # Сохраняет исходные единицы и типы JSON, отделяя данные потребителя от настроек проекта.
|
||
|
||
|
||
def flashing_profile(metadata, loader): # Возвращает параметры конкретного загрузчика одного проверенного выпуска.
|
||
return validate_flashing(metadata.get("flashing", {})).get("profiles", {}).get(loader, {}) # Старый manifest без flashing продолжает работать с прежними ручными настройками.
|