Собрано из SETGUI/src/gui_desktop/core (protocan, can_transport, protocol, gas_catalog); в CAN_to_RS485/template/python лежала такая же копия. Только stdlib: ни Qt, ни pyserial — модулям передают bytes, порт и таймауты остаются делом вызывающего кода. Кодировщики совпадают побайтово с c/protocan-transport, что зафиксировано эталонами в его тестах. Добавлен __init__.py: gas_catalog импортирует protocol относительным импортом, без пакета копия в CAN_to_RS485/template не собиралась.
58 lines
2.8 KiB
Markdown
58 lines
2.8 KiB
Markdown
# 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`, здесь — источник истины.
|