# protocan (Python) Хостовая сторона протоколов: разбор сообщений **ProtoCAN**, транспортный кадр моста CAN↔RS485 и кодеки каталога общего адресного пространства. Только stdlib, Python 3.9+. Ни Qt, ни pyserial: модули не открывают порт и не знают, откуда пришли байты, — им передают `bytes`. Поэтому они проверяются обычными host-тестами и одинаково работают в GUI, в скрипте и в CI. ## Состав | Модуль | Что делает | Зеркало на C | |---|---|---| | `protocan.py` | разбор 29-битного идентификатора и тела всех типов сообщений в человекочитаемый вид | `pcan_id.c` | | `transport.py` | кадр `AA 55 … CRC16`, потоковый разборщик с ресинхронизацией, CRC-16/CCITT-FALSE | `pcan_frame.c` | | `protocol.py` | кадр `A5 5A` протокола SETGUI: сборка, разбор, CRC32 | `gui_frame.c` | | `gas_catalog.py` | каталог регистров с именами и подписка на поток значений | `gui_catalog.c` | Зависимость внутри пакета одна: `gas_catalog` использует `protocol`. Остальные модули независимы. Кодировщики C и Python дают побайтово одинаковый результат — это зафиксировано эталонами в `c/protocan-transport/tests/test_transport.c`. ## Быстрый старт ```python from protocan import protocan, transport parser = transport.FrameParser() for frame in parser.feed(port.read(256)): # bytes из любого источника print(protocan.decode(frame.can_id, frame.data).summary) port.write(transport.build_frame(0x18FF0102, b"\x01\x02").encode()) ``` Каталог регистров: ```python from protocan.gas_catalog import ObjectEntry, ObjectType, encode_entry, decode_entry entry = ObjectEntry(address=0x0100, type=ObjectType.U16, flags=0, scale_pow10=-1, unit=0, name="Температура") raw = encode_entry(entry) assert decode_entry(raw) == entry ``` ## Границы Разбор отделён от ввода-вывода намеренно: порт, таймауты и переподключение — дело вызывающего кода. Байты внутрь, объекты наружу. ## Проверено в проектах `SETGUI` (вкладки «Шина CAN» и «Адресное пространство»), `CAN_to_RS485`. Там пакет лежит внутри `gui_desktop/core`, здесь — источник истины.