import ctypes # Привязка передаёт готовые слова общему C-кодеку без повторения wire-формата на Python. from .native import _load_library # Стандартный поиск сохраняет существующий механизм доставки SETProtocol. class StmInvertorCodec: # Один экземпляр владеет только сигнатурами функций общей библиотеки. def __init__(self, library=None): # Явная DLL позволяет новой панели не заменять библиотеку работающего приложения. self.lib = library if library is not None else _load_library() # Нет Python fallback при отсутствии нового C-кодека. self.lib.set_invertor_gas_limit.argtypes = [ctypes.c_uint] # Тип сообщения передаётся без вычисления геометрии карты в Python. self.lib.set_invertor_gas_limit.restype = ctypes.c_uint # Границы определяются профилем C из Templates. self.lib.set_invertor_gas_build.argtypes = [ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_uint16), ctypes.c_uint, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint8)] # Типы совпадают с публичным C ABI общего адаптера. self.lib.set_invertor_gas_build.restype = ctypes.c_int # C возвращает фактический DLC либо отрицательную ошибку. self.lib.set_invertor_gas_decode.argtypes = [ctypes.c_uint32, ctypes.c_uint, ctypes.POINTER(ctypes.c_uint8), ctypes.c_uint, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint16)] # Декодер проверяет ID, тип, направление, длину и маску. self.lib.set_invertor_gas_decode.restype = ctypes.c_int # Число принятых wire-слов приходит только из C. def limit(self, message_type): # Интерфейс не хранит отдельную копию константы 250 или 750. return int(self.lib.set_invertor_gas_limit(message_type)) # Передаём актуальную границу виджету адреса. def build(self, message_type, address, words=()): # Пустой набор слов означает чтение DLC0 в обоих протоколах. if not 0 <= address <= 0xFFFF or len(words) > 4 or any(not 0 <= word <= 0xFFFF for word in words): # Проверяем представимость Python-чисел перед сужением до типов ABI. raise ValueError('Адрес или значение вне диапазона ABI') # Не допускаем тихого усечения и циклического переноса ctypes. source = (ctypes.c_uint16 * len(words))(*words) if words else None # Значения остаются словами, endian выбирает общий кодек. identifier = ctypes.c_uint32(); data = (ctypes.c_uint8 * 8)() # Локальные буферы имеют размеры Classic CAN и не переживают вызов. length = self.lib.set_invertor_gas_build(message_type, address, source, len(words), ctypes.byref(identifier), data) # Ни CAN ID, ни маска не строятся здесь вручную. if length < 0: # C отклоняет неверную геометрию, длину блока и маску. raise ValueError('GAS: неверный адрес, длина блока или маска Word0') # Пользователь видит причину отказа до передачи на шину. return identifier.value, bytes(data[:length]) # Транспорт получает полностью готовые ID и октеты. def decode(self, identifier, flags, payload): # Входной кадр проверяется до публикации значений интерфейсу. if not 0 <= identifier <= 0x1FFFFFFF or len(payload) > 8: # Не допускаем сужения большого CAN ID и переполнения временного буфера. return None # Кадры другого транспорта не считаются ответом инвертора. data = (ctypes.c_uint8 * 8)(*payload); metadata = (ctypes.c_uint * 2)(); words = (ctypes.c_uint16 * 4)() # ABI работает с фиксированными массивами без указателей на объекты Python. count = self.lib.set_invertor_gas_decode(identifier, flags, data, len(payload), metadata, words) # Wire-разбор остаётся единственной C-реализацией. return (metadata[0], metadata[1], tuple(words[:count])) if count > 0 else None # Недопустимый кадр не изменяет отображённые значения.