"""Серверный Python-порт DEIF: протокольное состояние целиком принадлежит общему C99.""" # Пакет не импортирует gui_desktop и пригоден без Qt. import ctypes as C # Структуры ABI содержат только готовые кадры и скаляры. from time import monotonic # Порт поставляет монотонные часы, чтобы C оставался независим от ОС. from .deif_display import DeifDisplay, FIELDS, STATUS, _milliseconds # Пассивный и активный режимы используют одинаковые снимки индикаторов. from .deif_native import Frame, get_deif_native # Нет Python-копии адресации, NMT, сроков или SDO. def validate_slave_transmit(frame, node): # Серверный маршрут имеет отдельный контракт по собственному Node-ID. if type(node) is not int or not 1 <= node <= 127 or not get_deif_native().validate(frame.can_id, bytes(frame.data), len(frame.data), int(bool(frame.ide)) | (int(bool(frame.rtr)) << 1), node): # C разрешает только собственные heartbeat, EMCY и ответы SDO. raise ValueError("Разрешены только heartbeat, EMCY и ответы SDO своего SLAVE") # Недопустимый кадр не попадёт в физический транспорт. class DeifSlave(DeifDisplay): # Отличие от монитора состоит в включённых сервисах C и сохранении последнего числа при Data lost. services, expiry_ms = True, 0 # Частоту heartbeat и срок Data lost определяет C, нулевой expiry сохраняет последнее показание. def __init__(self, node=11, source_node=1, xdi_net=True, self_start=True, dam_pdo=2, bindings=None, dimmer_group=1): # Сохраняет существующий Python API модели SLAVE. if any(type(value) is not int for value in (node, source_node, dam_pdo, dimmer_group)): # Порт проверяет типы до преобразования unsigned ABI. raise ValueError("Параметры профиля DEIF должны быть целыми") # Логические и дробные адреса не превращаются в другой узел. super().__init__(source="Локальная симуляция", device_node=node, bindings=bindings, source_node=source_node, xdi_net=xdi_net, self_start=self_start, dam_pdo=dam_pdo, dimmer_group=dimmer_group) # Все ограничения и исходные привязки создаются C-ядром. def start(self, now=None, reset_values=True): # Явное включение не использует физический транспорт напрямую. self._sync_bindings() # Последние параметры UI должны соответствовать создаваемому сеансу. self._api.start(self._context, _milliseconds(monotonic() if now is None else now), bool(reset_values)) # Boot-up и self-start формируются общим автоматом C. def stop(self): # Выключение прекращает в том числе невыданные служебные события. self._api.stop(self._context) # Никакого отдельного Python-таймера heartbeat здесь нет. def accept(self, source, identifier, data, extended=False, rtr=False, now=None): # Транспортное имя фильтруется базовым портом, байты принимает C. return super().accept(source, identifier, data, extended, rtr, monotonic() if now is None else now) # Явные часы позволяют одинаково проверять границы Python и JNI. def tick(self, now=None): # Порт регулярно выгружает готовые события и передаёт их выбранному транспорту. from .can_transport import build_frame # Формат транспорта не смешивается с форматом DEIF. self._sync_bindings() # Прямое изменение профиля не должно оставлять старые привязки. output = (Frame * 16)() # Вместимость соответствует ограниченному пакету событий публичного ABI. count = self._api.poll(self._context, _milliseconds(monotonic() if now is None else now), output, len(output)) # Все сроки и NMT-переходы обслуживаются C. return [build_frame(frame.can_id, bytes(frame.data[:frame.dlc]), ide=False) for frame in output[:count]] # Python меняет только тип контейнера готовых кадров. def lost_fields(self, now=None): # Локализованный список строится поверх готовой маски C. self._sync_bindings() # Непривязанное поле не должно участвовать в предупреждении. mask = self._api.lost(self._context, _milliseconds(monotonic() if now is None else now)) # Независимые сроки по 3000 мс определяются общим ядром. return [key for index, key in enumerate(FIELDS) if mask & (1 << index)] # Соответствие field ID и имени не повторяет алгоритм свежести. def _meta(self): # Набор скаляров не раскрывает внутреннюю структуру C. output = (C.c_int64 * 10)() # Число элементов закреплено в deif_xdi.h. self._api.meta(self._context, output) # Состояние хранится только в одном месте. return list(output) # Python-порт не пересчитывает NMT или периоды. active = property(lambda self: bool(self._meta()[0])) # Флаг нужен жизненному циклу страницы. state = property(lambda self: self._meta()[1]) # Готовый CANopen NMT-код используется в локализованной подписи. started = property(lambda self: self._meta()[2] / 1000) # Секунды сохраняют совместимость прежнего Python API. last_heartbeat = property(lambda self: self._meta()[3] / 1000) # Преобразуется только единица системного времени. heartbeat_ms = property(lambda self: self._meta()[4]) # Значение стандартного объекта 0x1017 выдаёт C. accepted = property(lambda self: self._meta()[5]) # Счётчик принятия не имитируется количеством тиков GUI. brightness = property(lambda self: self._meta()[7]) # Текущий процент подсветки из C-профиля. palette = property(lambda self: self._meta()[8]) # Day/Night/Dusk хранится как код общего ядра. invalid = property(lambda self: {key: STATUS[item[2]] for key, item in self._snapshots().items() if item[2]}) # Только текст статуса локализуется в Python.