import ctypes # Передаём построение ID, проверку маски и порядок октетов единственному C-кодеку. from .native import get_native_core # Используем штатный поиск библиотеки без резервного Python-кодека. class GasSlots: # Тип 0x4 содержит Word0 с маской и три значения в фиксированных позициях. def __init__(self, library=None): # Тест или приложение может передать ту же собранную библиотеку явно. self.lib = library or get_native_core().lib # Отсутствующее общее ядро проявляется штатной ошибкой загрузки. self.lib.pcan_gas_slot_request_id.argtypes = [ctypes.c_uint] * 3 # Обёртка PERIPH принимает mode, локальный слот и Route. self.lib.pcan_gas_slot_request_id.restype = ctypes.c_uint32 # Extended ID возвращается без знакового преобразования. self.lib.pcan_gas_slot_id.argtypes = [ctypes.c_uint] * 4 # Общий построитель принимает DeviceType, DeviceID, полный адрес и Route. self.lib.pcan_gas_slot_id.restype = ctypes.c_uint32 # Любой корректный ID типа 0x4 отличен от нулевого маркера ошибки. self.lib.pcan_gas_slot_encode.argtypes = [ctypes.POINTER(ctypes.c_uint16), ctypes.c_uint, ctypes.POINTER(ctypes.c_uint8)] # Четыре wire-слова включают маску, а не четвёртый регистр. self.lib.pcan_gas_slot_encode.restype = ctypes.c_int # Кодек возвращает длину 0/8 либо отрицательный отказ. self.lib.pcan_gas_slot_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_uint), ctypes.POINTER(ctypes.c_uint16)] # PERIPH-декодер дополнительно проверяет принадлежность странице и последний слот. self.lib.pcan_gas_slot_decode.restype = ctypes.c_int # Результат 0/4 включает маску при наличии данных. self.lib.pcan_gas_slot_decode_words.argtypes = [ctypes.c_uint32, ctypes.c_uint, ctypes.POINTER(ctypes.c_uint8), ctypes.c_uint, ctypes.POINTER(ctypes.c_uint16)] # Общий декодер поддерживает остальные классы узлов и все 16 бит адреса. self.lib.pcan_gas_slot_decode_words.restype = ctypes.c_int # Отрицательное значение означает отказ без публикации частичного результата. @staticmethod # Проверка представимости Python-чисел не дублирует wire-логику. def _unsigned(value, maximum): # Проверяем границы до возможного сужения ctypes. if type(value) is not int or not 0 <= value <= maximum: # bool, дробь и отрицательное число не являются полями протокола. raise ValueError('Недопустимое целое значение протокола') # Не отправляем усечённое значение к другому адресу. def _payload(self, words, route): # Общая упаковка используется адресным и PERIPH-построителями. values = None if words is None else tuple(words) # Фиксируем входной iterable до преобразования к C-массиву. if values is not None: # Запись или ответ содержат ровно маску и три значения. if len(values) != 4: # Позиции не сдвигаются при частичной записи. raise ValueError('Нужны Word0-маска и три значения Word1…Word3') # Не дополняем отсутствующие позиции нулями неявно. for value in values: # Все wire-слова имеют одинаковую ширину 16 бит. self._unsigned(value, 0xFFFF) # Корректные ноль и 65535 остаются допустимыми значениями. if values is None and route != 0: # Пустой ответ прибора не имеет определённого смысла. raise ValueError('Ответ должен содержать маску и значения') # Отказ возникает до отправки в транспорт. source = None if values is None else (ctypes.c_uint16 * 4)(*values) # Маску и значения проверяет C до кодирования. data = (ctypes.c_uint8 * 8)() # Буфер ограничен одним classic CAN кадром. length = self.lib.pcan_gas_slot_encode(source, 0 if values is None else 4, data) # Единственный кодек проверяет разрешённые биты маски и порядок октетов. if length < 0: # Неверная маска не заменяется другой командой. raise ValueError('Неверная маска GAS SLOT: допустимы непустые биты 1…3') # Бит 0 относится к служебному слову и запрещён. return bytes(data[:length]) # Возвращаем только готовые wire-октеты. def frame(self, mode, slot, words=None, route=0): # Удобная обёртка для 200 регистров каждого mode PERIPH. for value in (mode, slot, route): # Исключаем переполнение host ABI до вызова C. self._unsigned(value, 0xFFFFFFFF) # Семантические пределы mode и local проверяет общий построитель. can_id = self.lib.pcan_gas_slot_request_id(mode, slot, route) # C формирует MsgType=0x4 и HEX-страницу mode. if can_id == 0: # Не создаём адрес для резерва страницы или неверного направления. raise ValueError('Неверный mode, слот или направление') # Исходная ошибка не превращается в обращение к соседнему прибору. payload = self._payload(words, route) # Данные одинаковы для обоих адресных API. self.decode(can_id, payload) # Дополнительно проверяем маску двух действующих значений последнего слота PERIPH. return can_id, payload # Транспорт получает готовые CAN ID и данные. def frame_address(self, device_type, device_id, address, words=None, route=0): # Полный слотный API для любого узла, включая STM Invertor. for value in (device_type, device_id, address, route): # Python-числа не должны оборачиваться в C unsigned. self._unsigned(value, 0xFFFFFFFF) # Конкретную ширину полей идентификатора проверяет C. can_id = self.lib.pcan_gas_slot_id(device_type, device_id, address, route) # Общий построитель сохраняет все адреса 0…65535. if can_id == 0: # Нулевой результат означает ошибку адреса узла или слота. raise ValueError('Неверный адрес узла, слота или направление') # Не скрываем ошибку входа маскированием. return can_id, self._payload(words, route) # Не привязываем общий формат к странице PERIPH. def _input(self, can_id, data, flags): # Проверяем размеры и представимость до создания ctypes-буферов. self._unsigned(can_id, 0xFFFFFFFF) # C затем проверит фактические 29 бит CAN ID. self._unsigned(flags, 0xFFFFFFFF) # Флаги IDE/RTR/ERR проверяются общим wire-кодеком. payload = bytes(data) # Сохраняем точную длину без усечения. if len(payload) > 8: # CAN FD не включается неявно. raise ValueError('Classic CAN допускает до восьми октетов') # Лишние октеты остаются видимой ошибкой. return (ctypes.c_uint8 * len(payload)).from_buffer_copy(payload), len(payload) # Нулевой запрос не требует фиктивных данных. def decode(self, can_id, data, flags=1): # Возвращаем mode, локальный слот и wire-слова PERIPH с маской. raw, length = self._input(can_id, data, flags) # Получаем проверенный ограниченный буфер. mode, slot, words = ctypes.c_uint(), ctypes.c_uint(), (ctypes.c_uint16 * 4)() # Отдельные выходы сохраняют ширину host ABI. count = self.lib.pcan_gas_slot_decode(can_id, flags, raw, length, ctypes.byref(mode), ctypes.byref(slot), words) # C проверяет полный формат и геометрию PERIPH. if count < 0: # Чужая страница или несуществующая позиция не становятся регистрами другого профиля. raise ValueError('Неверный кадр GAS SLOT PERIPH') # Никакого автоматического перехода к типу 0x3. return mode.value, slot.value, tuple(words[:count]) # Пустой кортеж означает запрос чтения. def decode_address(self, can_id, data, flags=1): # Декодируем маску и три значения любого адресуемого узла. raw, length = self._input(can_id, data, flags) # Проверяем точную длину до native-вызова. words = (ctypes.c_uint16 * 4)() # Четыре wire-слова включают служебную маску. count = self.lib.pcan_gas_slot_decode_words(can_id, flags, raw, length, words) # Общий C-кодек проверяет тип 0x4 и все транспортные условия. if count < 0: # Ошибка не выдаётся за допустимый пустой слот. raise ValueError('Неверный кадр GAS SLOT') # Сообщение пригодно для CAN-журнала. return tuple(words[:count]) # Адрес и класс узла уже доступны вызывающему коду в CAN ID.