"""Готовые операции DEIF для GUI без знания wire-разметки и адресных масок.""" # Все числовые правила реализованы единственным C99-ядром templates. import ctypes as C # Преобразование фиксированных ABI-структур не является вторым кодеком. from dataclasses import dataclass # Именованные поля исключают индексы метаданных в GUI. from .deif_native import get_deif_native, Frame # Общий декодер и builders используются независимо от приложения. from .can_transport import build_frame # Транспорт получает уже готовые CAN-кадры. @dataclass(frozen=True) # Неизменяемые описания безопасно используются диагностикой и редакторами. class SelectorInfo: # Это скалярный результат C, а не wire-структура. node: int; index: int; subindex: int; dam: bool; legacy_key: str # Последний элемент сохраняет только формат прежних ключей настроек Desktop. @dataclass(frozen=True) # Описание входного кадра отделено от его транспортного контейнера. class PacketInfo: # GUI не обращается к смещениям payload. kind: int; node: int; index: int; subindex: int; raw: int; status: int; dam: bool # Все значения получены из C-декодера. @property # Диагностический ключ независим от адресного байта SAM/DAM. def object_key(self): # Для TX-эхо достаточно индекса и подиндекса готового объекта. return self.index, self.subindex # Это идентичность объекта, а не повторная распаковка CAN-data. def selector_info(selector): # Редактор получает имена полей вместо ручных масок. raw = bytes.fromhex(selector) if isinstance(selector, str) else bytes(selector) # HEX — только формат хранения пользовательской настройки. if len(raw) != 4: return None # Буфер неправильного размера не передаётся C. metadata = (C.c_int32 * 4)() # Node, index, subindex и DAM определены публичным ABI. if not get_deif_native().selector_info(raw, metadata): return None # Некорректная привязка не становится источником на рисунке. return SelectorInfo(metadata[0], metadata[1], metadata[2], bool(metadata[3]), raw[1:].hex()) # Срез используется только для совместимости непрозрачного старого ключа настроек. def readdress_selector(selector, node, dam=None): # Меняет адрес пользовательской привязки общим алгоритмом C. if not selector: return "" # Явно отключённое поле остаётся отключённым. raw = bytes.fromhex(selector) if isinstance(selector, str) else bytes(selector) # Порт преобразует текст настройки в буфер ABI. output = (C.c_uint8 * 4)() # Готовый селектор создаёт C-код. if len(raw) != 4 or not get_deif_native().readdress(raw, node, 2 if dam is None else int(bool(dam)), output): raise ValueError("Неверный селектор или Node-ID DEIF") # Индекс не пересобирается в Python. return bytes(output).hex(" ").upper() # Формат вывода совместим с прежним редактором HEX. def route_parameters(node, mode, identifier, operation=0): # Согласует параметры редактора с C-правилами адресации. output = (C.c_uint32 * 3)() # COB-ID, Node и адресный байт возвращаются одним вызовом. if not get_deif_native().route(node, mode == "dam", identifier, operation, output): return None # Неверный диапазон не вызывает произвольную перенастройку привязок. return tuple(output) # GUI хранит и отображает готовые значения. def data_node(identifier, fallback=1): # Получает узел из CAN ID без повторения маски в GUI. result = route_parameters(fallback or 1, "sam", identifier, 2) # Legacy SAM сохраняет явно выбранный источник. return result[1] if result else fallback # Вне диапазона данных остаётся пользовательское исходное значение. def packet_info(frame): # Метаданные для подтверждения команд и TX-эхо. code, metadata = get_deif_native().inspect_frame(frame.can_id, frame.data, frame.ide, frame.rtr) # Wire-разбор находится в C. return PacketInfo(metadata[0], metadata[1], metadata[2], metadata[3], metadata[4], metadata[7], bool(metadata[8])) if code > 0 else None # Некорректный кадр не получает выдуманных полей. def heartbeat_description(identifier, data, extended=False, rtr=False): # Приложение не содержит собственного классификатора CANopen error control. code, _ = get_deif_native().inspect_frame(identifier, data, extended, rtr) # Проверку COB-ID, DLC и состояния выполняет C. if code not in (2, -3): return None # Обычные MPDO не попадают в поле heartbeat. from .deif_xdi import decode # Общий локализованный formatter используется обоими CAN-мониторами и прибором. return decode(build_frame(identifier, bytes(data), ide=extended, rtr=rtr)).summary # Готовая подпись не подтверждает получение конкретного измерения. def _frame(output): # Общий адаптер переводит структуру C в существующий транспортный контейнер. return build_frame(output.can_id, bytes(output.data[:output.dlc]), ide=False) # Идентификатор, длина и данные уже вычислены ядром. def service_frame(node, value, nmt=False): # Строит heartbeat либо локальную NMT-команду через C. output = Frame() # Память результата принадлежит Python-порту. if not get_deif_native().service(bool(nmt), node, value, C.byref(output)): raise ValueError("Неверный Node-ID или CANopen-сервис") # Ни COB-ID, ни служебные байты не собираются в GUI. return _frame(output) # Физическую отправку выполняет выбранный приложением транспорт. def angle_setpoint_frame(actual): # Формирует отдельный кадр задания с тем же значением фактического азимута. source, output = Frame(), Frame() # Оба контейнера имеют стабильный layout ABI. source.can_id, source.dlc, source.data[:] = actual.can_id, len(actual.data), bytes(actual.data) # Это копирование готового CAN-кадра, не декодирование его полей. if not get_deif_native().angle_command(C.byref(source), C.byref(output)): raise ValueError("Кадр не содержит поддерживаемый азимут DEIF") # Проверку семейства и сборку 0x07 выполняет C. return _frame(output) # Штатный передатчик получает второй независимый MPDO.