"""Тонкий ctypes-порт единственного C99-ядра DEIF из templates/c/set-protocol.""" # В Python нет второго кодека или автомата NMT. import ctypes as C # Фиксированные типы повторяют только публичный ABI. import os # Путь дополнительной библиотеки передаёт приложение, не общая библиотека. from pathlib import Path # Дополнительный нативный компонент может лежать рядом с основной библиотекой любого приложения. from protocan.native import _load_library # Стандартный механизм загрузки используется при отсутствии явного пути DEIF. class Frame(C.Structure): # Структура содержит готовые STD-кадры, а не wire-структуру транспорта. _fields_ = [("can_id", C.c_uint32), ("dlc", C.c_uint32), ("data", C.c_uint8 * 8)] # Layout размером 16 байт совпадает с deif_xdi.h. class NativeDeif: # Экземпляр владеет только сигнатурами C, состояние выделяется отдельными моделями. def __init__(self): # Отсутствие общего ядра является ошибкой сборки, алгоритмического fallback нет. self.lib = C.CDLL(os.environ["SETPROTOCOL_DEIF_LIBRARY"]) if os.environ.get("SETPROTOCOL_DEIF_LIBRARY") else _load_library() # Выбор пути относится к загрузке, не к протоколу. if not hasattr(self.lib, "setp_deif_version"): # Во время обновления основная DLL может оставаться занятой работающим приложением. primary = Path(self.lib._name) # Путь сообщает стандартный загрузчик, структура gui_desktop здесь неизвестна. self.lib = C.CDLL(str(primary.with_name(primary.stem + "_deif" + primary.suffix))) # Это дополнительная сборка того же C-ядра, а не алгоритмический Python fallback. signatures = { # Сигнатуры скалярного ABI не зависят от внутренних C-структур. "version": ([], C.c_int), "state_size": ([], C.c_size_t), # Проверка версии и caller-owned памяти. "catalog": ([C.c_uint, C.POINTER(C.c_int32)], C.c_int), # Каталог числовых индексов и разрешений. "selector": ([C.c_uint, C.c_uint, C.c_uint, C.c_uint, C.c_void_p], C.c_int), # C формирует готовый селектор. "inspect": ([C.c_uint32, C.c_void_p, C.c_size_t, C.c_uint, C.POINTER(C.c_int32)], C.c_int), # Единственный разбор входящего wire-кадра. "make": ([C.c_uint, C.c_uint, C.c_uint, C.c_int32, C.c_uint32, C.c_uint, C.POINTER(Frame)], C.c_int), # Единственная сборка MPDO. "master": ([C.c_uint, C.POINTER(C.c_int32), C.c_uint32, C.c_uint, C.c_uint, C.POINTER(Frame)], C.c_int), # Единственный порядок пяти объектов цикла. "service": ([C.c_uint, C.c_uint, C.c_uint, C.POINTER(Frame)], C.c_int), "angle_command": ([C.POINTER(Frame), C.POINTER(Frame)], C.c_int), # Служебные кадры и задание азимута собираются общим C-кодом. "selector_info": ([C.c_void_p, C.POINTER(C.c_int32)], C.c_int), "route": ([C.c_uint, C.c_uint, C.c_uint32, C.c_uint, C.POINTER(C.c_uint32)], C.c_int), # Редакторы не разбирают адресный байт или COB-ID самостоятельно. "readdress": ([C.c_void_p, C.c_uint, C.c_uint, C.c_void_p], C.c_int), # Перенос селектора сохраняет ручной объект без масок и endian в UI. "validate": ([C.c_uint32, C.c_void_p, C.c_size_t, C.c_uint, C.c_uint], C.c_int), # Изолированные выходы MASTER и SLAVE. "init": ([C.c_void_p, C.c_size_t] + [C.c_uint] * 7 + [C.c_uint32], C.c_int), # Семь параметров профиля и отдельный CAN ID наблюдателя. "bind": ([C.c_void_p, C.c_uint, C.c_void_p, C.c_double], C.c_int), # Селектор и библиотечный масштаб передаются без интерпретации. "get_binding": ([C.c_void_p, C.c_uint, C.c_void_p, C.POINTER(C.c_double)], C.c_int), # Исходные значения профиля приходят из единственного C-каталога. "default_angle": ([C.c_void_p, C.c_void_p, C.POINTER(C.c_double)], C.c_int), # Общая миграция отсутствующей привязки. "start": ([C.c_void_p, C.c_uint64, C.c_uint], C.c_int), "stop": ([C.c_void_p], None), "reset_values": ([C.c_void_p], None), # Жизненный цикл без системных часов C. "accept": ([C.c_void_p, C.c_uint32, C.c_void_p, C.c_size_t, C.c_uint, C.c_uint64], C.c_int), # C проверяет NMT, адресацию, объекты и длину. "poll": ([C.c_void_p, C.c_uint64, C.POINTER(Frame), C.c_size_t], C.c_size_t), # Порт владеет выходным буфером событий. "read": ([C.c_void_p, C.c_uint, C.c_uint64, C.c_uint, C.POINTER(C.c_double), C.POINTER(C.c_int64)], C.c_int), # Готовое значение и временные метаданные индикатора. "lost": ([C.c_void_p, C.c_uint64], C.c_uint), # Маска устаревших входов вычисляется в C. "angle": ([C.c_void_p, C.c_uint64, C.c_uint, C.POINTER(C.c_double)], C.c_int), "meta": ([C.c_void_p, C.POINTER(C.c_int64)], C.c_int), # Единый выбор азимута и снимок состояния. } # Эти объявления описывают ABI, а не копию алгоритма. for name, (arguments, result) in signatures.items(): # Все отсутствующие символы выявляются при загрузке расширения. function = getattr(self.lib, "setp_deif_" + name) # Названия совпадают с публичным заголовком templates. function.argtypes, function.restype = arguments, result # ctypes не должен угадывать размеры указателей и целых. setattr(self, name, function) # Короткие имена оставляют порты моделей читаемыми. if self.version() != 1: # Несовместимый ABI не используется с предположением о layout. raise RuntimeError("Нужна общая библиотека DEIF ABI 1") # Сообщение указывает на необходимость пересборки нативного ядра. def selector_bytes(self, field, node, instance=1, dam=False): # Возвращает готовый C-селектор для настроек UI. output = (C.c_uint8 * 4)() # Размер мультиплексора задан публичным контрактом. if not self.selector(field, node, instance, bool(dam), output): # Проверка диапазонов находится в C. raise ValueError("DEIF: неверный Node-ID, объект или экземпляр") # Недопустимая привязка не сохраняется в настройках. return bytes(output) # Порт преобразует только тип контейнера. def inspect_frame(self, identifier, data, extended=False, rtr=False): # Передаёт целый CAN-кадр общему декодеру. if not 0 <= identifier <= 0x1FFFFFFF or len(data) > 8: # Проверяет представимость на границе ABI до сужения Python-чисел. return -1, [0] * 12 # Переполнение нельзя превратить в другой допустимый кадр. output = (C.c_int32 * 12)() # Индексы метаданных определены в контракте C-декодера. code = self.inspect(identifier, bytes(data), len(data), int(bool(extended)) | (int(bool(rtr)) << 1), output) # Флаги относятся к типу CAN-кадра, не к содержимому протокола. return code, list(output) # Локализованный текст формируется поверх готовых полей C. def make_frame(self, field, node, value, identifier, mode="sam", instance=1): # Возвращает кадр существующего транспортного API. from .can_transport import build_frame # Пакет транспорта не импортируется до фактической сборки кадра. if mode not in ("sam", "dam") or type(instance) is not int or not 0 <= instance <= 4294967295 or type(value) is not int or not -2147483648 <= value <= 2147483647 or type(node) is not int or not 0 <= node <= 127 or type(identifier) is not int or not 0 <= identifier <= 0x1FFFFFFF: # Порт проверяет только типы и представимость ABI. raise ValueError("DEIF: неверный Node-ID, режим, CAN ID или значение") # Значения не усекаются до вызова C. output = Frame() # Память выходного CAN-кадра принадлежит Python. if not self.make(field, node, instance, value, identifier, mode == "dam", C.byref(output)): # Wire-байты и границы протокола определяются исключительно C. raise ValueError("DEIF: Node-ID, RPDO, экземпляр или значение вне диапазона") # Причина отклонения остаётся видимой в интерфейсе. return build_frame(output.can_id, bytes(output.data[:output.dlc]), ide=False) # Транспорт AA55/адаптер получает уже готовые данные STD. _native = None # Общая библиотека загружается один раз на процесс. def get_deif_native(): # Ленивое получение не требует Qt и пригодно для CLI. global _native # Экземпляр хранит сигнатуры, состояния приборов остаются раздельными. if _native is None: # Первая реальная операция DEIF проверяет наличие нового нативного ядра. _native = NativeDeif() # Отсутствие DLL или ABI не скрывается Python-реализацией протокола. return _native # Все модели используют один и тот же проверенный C-код.