"""Настройки формы прошивки из 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 продолжает работать с прежними ручными настройками.