Единое ядро форматов
Windows, Android и MCU больше не реализуют CRC, порядок байт и ресинхронизацию каждый по-своему.
Одно C99-ядро для SETGUI, Android, Linux и микроконтроллеров. SET v2, ProtoCAN и GUI v1 собраны вместе; COM, SLCAN, SocketCAN и HAL подключаются снаружи.
SETProtocol — не драйвер адаптера и не GUI framework. Это детерминированная протокольная часть, одинаковая для каждой программы и платы.
Windows, Android и MCU больше не реализуют CRC, порядок байт и ресинхронизацию каждый по-своему.
Смена COM на SocketCAN заменяет адаптер ввода-вывода, но не декодер протокола и не тестовые векторы.
FFI использует публичный setprotocol_abi.h версии 1; внутренние C-структуры наружу не протекают.
500000 у COM — скорость последовательного интерфейса адаптера. CAN bitrate на линии задаётся отдельно в адаптере/драйвере. SETProtocol получает уже доставленные байты или CAN-кадры.can_id + flags + data. После этого таблица, фильтры и ProtoCAN-декодер общие.Нижний слой не знает, кто его вызвал. Верхний слой не должен знать внутреннюю раскладку parser context.
pcan_id упаковывает Priority, Route, DeviceType, DeviceID, MsgType и Body в Extended ID без непереносимых bit-fields.
pcan_frame кодирует и разбирает AA55; pcan_crc даёт CRC16; pcan_link ведёт SEQ и статистику.
gui_frame реализует отдельный A55A transport с payload до 512 байт и CRC32.
pcan_gas входит в shared core и отображает данные в 16-битное пространство. gui_catalog — опциональный C-модуль вне DLL.
pcan_ring — SPSC-кольцо с непрерывным участком, удобным для DMA. Размер буфера — степень двойки.
pcan_abi экспортирует только скалярные значения и буферы известных размеров для ctypes/JNI/Swift.
| Вне ядра | Почему | Где реализовать |
|---|---|---|
| COM/SLCAN/SocketCAN | ОС и конкретный адаптер | desktop/android port |
| CAN bitrate и UART baud | Настройка физического интерфейса | driver/config UI |
| HAL, IRQ, DMA | Зависят от MCU и SDK | ports/<platform> |
| Вкладки и графики | Представление, не протокол | SETGUI / Android GUI |
| Firmware A/B flow | Отдельная предметная библиотека | protocan-boot |
SET v2 — основной протокол новых устройств. ProtoCAN bridge и GUI v1 остаются для совместимости на время перехода.
Единый кадр для управления, телеметрии и firmware flow поверх serial, USB, Ethernet и сегментированного CAN.
schema u16 · class u16 · hardware u32 · firmware u32 · dictionary u32 · serial u64 · model_length u8 · model UTF-8
Модель ограничена 63 байтами. Числа little-endian; body идёт после обязательного status u16.
schema u16 · MTU u16 · interfaces u32 · features u32 · read/write/subscription/publish limits u16
Флаги объявляют READ, WRITE, CATALOG, SUBSCRIBE, LOG_READ, FIRMWARE и DIAGNOSTICS. GUI включает только реально доступные функции.
LEN = 6 + DLC, максимум 19 байт. CRC-16/CCITT-FALSE считается от LEN до DATA.
Версия 1. CRC32 IEEE как у zlib. Это самостоятельный протокол, не CAN DLC.
Parser принимает один байт, половину кадра или несколько кадров подряд. Граница read() не имеет протокольного смысла.
После шума parser снова ищет SOF, отбрасывает неверную длину/CRC и продолжает поток, сохраняя диагностические счётчики.
Биты 28…0 упаковываются масками и сдвигами. Это исключает зависимость от реализации C bit-fields.
Приложение сначала проверяет версию, затем работает через функции из pcan_abi.h. Внутренние структуры можно менять, сохраняя ABI.
| Группа | Функции | Контракт |
|---|---|---|
| Версия и ID | version, id_pack, id_unpack | Скалярные аргументы, 29-битный результат |
| CAN frame | crc16, frame_encode | Caller-owned input/output buffers |
| CAN parser | parser_size/init/push/stats | Opaque caller-owned context |
| GUI frame | gui_crc32, gui_frame_encode | Payload не более 512 байт |
| GUI parser | gui_parser_size/init/push/stats | Opaque caller-owned context |
Ядро не выделяет память. Python использует create_string_buffer, MCU — static/stack, JNI хранит allocation только в адаптере.
Каждая линия имеет собственный context. Можно одновременно держать COM bridge, GUI transport и несколько устройств.
Один context — один владелец потока. Если владельцев несколько, блокировку добавляет приложение.
size_t bytes = pcan_abi_parser_size();
void *context = allocate_on_host_or_static_storage(bytes);
pcan_abi_parser_init(context, bytes);
if (pcan_abi_parser_push(context, next_byte, &frame) == 1) {
/* frame полностью проверен */
}
Переносимость ядра и готовность полной упаковки приложения — разные вещи. Здесь они разделены честно.
| Цель | Библиотека | Адаптер | Статус |
|---|---|---|---|
| Windows | setprotocol.dll | Python ctypes / COM | проверено |
| Android | libsetprotocol.so | JNI + Kotlin | 4 ABI |
| Linux | libsetprotocol.so | ctypes; нужен pyserial/SocketCAN port | ядро готово |
| macOS | libsetprotocol.dylib | ctypes/FFI | исходники готовы |
| STM32F4 | статический C99 | UART + DMA | порт есть |
| Другой MCU | статический C99 | I/O callbacks | нужен порт |
| iOS | C ABI | Swift wrapper | не добавлен |
libsetprotocol.so, указать SETPROTOCOL_LIBRARY, затем добавить backend физической линии. Для прямого CAN логичен SocketCAN; для USB-COM моста — pyserial.CMake создаёт static core, shared ABI и тесты из одного набора C99-файлов.
Подключить репозиторий как Git submodule и зафиксировать проверенный commit.
На host — CMake или build_host.py; на Android — NDK; на MCU — добавить исходники в проект.
COM, SLCAN, SocketCAN, USB или HAL только доставляет данные через узкую границу.
C tests, ABI vectors, Python native tests, Android build и smoke-test реальной линии.
cmake -S c/set-protocol -B build/setprotocol -DSETP_BUILD_TESTS=ON
cmake --build build/setprotocol --config Release
ctest --test-dir build/setprotocol -C Release --output-on-failure
python3 c/set-protocol/tools/build_host.py \
--output native/libsetprotocol.so
export SETPROTOCOL_LIBRARY="$PWD/native/libsetprotocol.so"
from protocan.native import NativeProtocol
core = NativeProtocol()
wire = core.encode(1, 1, 0x1234567, b"\xAA\xBB")
frames = core.parser().feed(wire)
Раздел генерируется из doc/CAN_FRAME_PARSE_V1_V2.md. В нём собраны wire-разметка, примеры и готовые Python-парсеры.
Документ описывает wire-форматы двух протоколов обновления прошивки:
templates/c/protocan-boot, одна команда или 8 байт образа в одном
Extended CAN-кадре;templates/c/set-protocol, полный кадр SETProtocol разбивается на
несколько Extended CAN-кадров.Все многобайтные поля payload передаются little-endian. CAN ID — 29-битный.
Для рабочего кода нужно использовать канонические реализации из templates,
а приведённый ниже Python-парсер удобен для анализатора, логов и отладки.
bits size field
28 1 Priority
27 1 Route: 0 = host -> device, 1 = device -> host
26..24 3 Device Type
23..20 4 Device ID
19..16 4 Message Type
15..0 16 Message Body
Формула:
ID = Priority << 28 |
Route << 27 |
DeviceType << 24 |
DeviceID << 20 |
MessageType << 16 |
MessageBody
Типы загрузочных сообщений:
| Message Type | Имя | Message Body | CAN payload |
|---|---|---|---|
0x9 |
BOOT_CONTROL |
SessionID << 8 \| Command |
параметры команды |
0xA |
BOOT_DATA_A |
индекс блока | 8 байт слота A |
0xB |
BOOT_DATA_B |
индекс блока | 8 байт слота B |
0xC |
BOOT_STATUS |
SessionID << 8 \| Command |
статус и прогресс |
0xD |
BOOT_DISCOVERY |
подтип | информация об устройстве |
Команды BOOT_CONTROL:
| Код | Команда | Payload |
|---|---|---|
0x01 |
IDENTIFY |
пустой |
0x02 |
ENTER_BOOT |
пустой |
0x03 |
BEGIN_IMAGE |
image_size u32, image_crc32 u32 |
0x04 |
BEGIN_COMPAT |
product u16, hw_min u8, hw_max u8, version u32 |
0x05 |
ERASE |
пустой |
0x06 |
VERIFY |
пустой |
0x07 |
COMMIT |
пустой |
0x08 |
CONFIRM |
пустой |
0x09 |
REBOOT |
пустой |
0x0A |
ABORT |
пустой |
0x0B |
QUERY_PROGRESS |
пустой |
BOOT_STATUS всегда содержит 8 байт:
offset size field
0 1 status
1 1 target_slot
2 2 next_block u16 LE
4 4 running_crc32 u32 LE
BOOT_DISCOVERY с body 1 содержит:
offset size field
0 2 product_type u16 LE
2 1 hardware_revision
3 1 protocol_version = 1
4 4 firmware_version u32 LE
Пример запроса IDENTIFY для DeviceType=7, DeviceID=13:
CAN ID: 17D90001
DLC: 0
def parse_v1(can_id: int, data: bytes) -> dict:
if not 0 <= can_id <= 0x1FFFFFFF:
raise ValueError("неверный Extended CAN ID")
if len(data) > 8:
raise ValueError("DLC больше 8")
result = {
"version": 1,
"priority": (can_id >> 28) & 0x01,
"route": (can_id >> 27) & 0x01,
"device_type": (can_id >> 24) & 0x07,
"device_id": (can_id >> 20) & 0x0F,
"message_type": (can_id >> 16) & 0x0F,
"message_body": can_id & 0xFFFF,
"data": bytes(data),
}
msg_type = result["message_type"]
body = result["message_body"]
if msg_type in (0x9, 0xC):
result["session_id"] = (body >> 8) & 0xFF
result["command"] = body & 0xFF
elif msg_type in (0xA, 0xB):
result["slot"] = msg_type - 0xA
result["block_index"] = body
if msg_type == 0xC:
if len(data) != 8:
raise ValueError("BOOT_STATUS должен содержать 8 байт")
result.update({
"status": data[0],
"target_slot": data[1],
"next_block": int.from_bytes(data[2:4], "little"),
"running_crc32": int.from_bytes(data[4:8], "little"),
})
elif msg_type == 0xD and body == 1:
if len(data) != 8:
raise ValueError("BOOT_DISCOVERY должен содержать 8 байт")
result.update({
"product_type": int.from_bytes(data[0:2], "little"),
"hardware_revision": data[2],
"protocol_version": data[3],
"firmware_version": int.from_bytes(data[4:8], "little"),
})
return result
В v2 CAN-кадр является только транспортным сегментом. Сначала нужно собрать полный SETP-пакет, и только затем разбирать его заголовок, payload и CRC32.
bits size field
28..24 5 Prefix = 0x12
23..16 8 Destination node
15..8 8 Source node
7 1 Priority
6..0 7 Channel
Формула:
ID = 0x12 << 24 |
Destination << 16 |
Source << 8 |
Priority << 7 |
Channel
Первый байт CAN payload — PCI:
| PCI | Назначение | Формат CAN payload |
|---|---|---|
0x10 |
первый сегмент | 10, total_length u16 LE, первые 5 байт SETP |
0x20..0x2F |
продолжение | 2N, следующие 1–7 байт SETP |
0x30..0x32 |
flow control | 3S, block_size, st_min_ms |
N — циклический номер сегмента 1..15,0..; следующий сегмент обязан иметь
ожидаемый номер, тот же CAN ID и прийти до тайм-аута сборки 500 мс.
offset size field
0 2 SOF = A5 5A
2 1 version = 02
3 1 flags
4 2 message_type u16 LE
6 2 source u16 LE
8 2 destination u16 LE
10 2 sequence u16 LE
12 2 payload_length u16 LE
14 N payload
14+N 4 CRC32 IEEE u32 LE
CRC32 считается по байтам от version на offset 2 до конца payload. Поля
source, destination и priority внутреннего заголовка должны совпадать с
CAN ID.
Флаги:
| Бит | Значение |
|---|---|
0x01 |
RESPONSE |
0x02 |
EVENT |
0x04 |
ERROR |
0x08 |
ACK_REQUIRED |
0x10 |
MORE |
0x20 |
PRIORITY |
Каждый response начинается с status u16 LE. Основные firmware message types:
FW_BEGIN=0x0100, FW_DATA=0x0101, FW_END=0x0102, FW_ABORT=0x0103,
FW_STATUS=0x0104, FW_ACTIVATE=0x0105.
Пример PING к BALZAM node 13, source 0, sequence 1, priority 1,
channel 1:
Полный SETP:
A5 5A 02 28 01 00 00 00 0D 00 01 00 00 00 E7 29 51 40
CAN ID 120D0081, сегменты:
10 12 00 A5 5A 02 28 01
21 00 00 00 0D 00 01 00
22 00 00 E7 29 51 40
import binascii
def parse_v2_can_id(can_id: int) -> dict:
if not 0 <= can_id <= 0x1FFFFFFF:
raise ValueError("неверный Extended CAN ID")
if (can_id >> 24) & 0x1F != 0x12:
raise ValueError("не SETProtocol v2 CAN ID")
return {
"destination": (can_id >> 16) & 0xFF,
"source": (can_id >> 8) & 0xFF,
"priority": (can_id >> 7) & 0x01,
"channel": can_id & 0x7F,
}
def parse_setp(packet: bytes, can_id: int) -> dict:
if len(packet) < 18 or packet[:2] != b"\xA5\x5A":
raise ValueError("нет полного SETP-кадра")
if packet[2] != 2:
raise ValueError("неподдерживаемая версия SETP")
flags = packet[3]
if flags & 0xC0:
raise ValueError("установлены зарезервированные флаги")
payload_length = int.from_bytes(packet[12:14], "little")
if len(packet) != 14 + payload_length + 4:
raise ValueError("не совпадает payload_length")
expected_crc = int.from_bytes(packet[-4:], "little")
actual_crc = binascii.crc32(packet[2:-4]) & 0xFFFFFFFF
if actual_crc != expected_crc:
raise ValueError("ошибка CRC32 SETP")
address = parse_v2_can_id(can_id)
source = int.from_bytes(packet[6:8], "little")
destination = int.from_bytes(packet[8:10], "little")
priority = int(bool(flags & 0x20))
if (source, destination, priority) != (
address["source"], address["destination"], address["priority"]
):
raise ValueError("SETP header не совпадает с CAN ID")
payload = packet[14:-4]
result = {
"version": 2,
"flags": flags,
"message_type": int.from_bytes(packet[4:6], "little"),
"source": source,
"destination": destination,
"sequence": int.from_bytes(packet[10:12], "little"),
"payload": payload,
"can": address,
}
if flags & 0x01:
if len(payload) < 2:
raise ValueError("response не содержит status")
result["status"] = int.from_bytes(payload[:2], "little")
result["body"] = payload[2:]
return result
class V2CanReassembler:
def __init__(self, timeout_ms: int = 500):
self.timeout_ms = timeout_ms
self.reset()
def reset(self):
self.can_id = None
self.total = 0
self.data = bytearray()
self.next_sequence = 1
self.deadline_ms = 0
def feed(self, can_id: int, data: bytes, now_ms: int):
parse_v2_can_id(can_id)
if not 1 <= len(data) <= 8:
raise ValueError("DLC вне диапазона 1..8")
if self.can_id is not None and now_ms >= self.deadline_ms:
self.reset()
raise ValueError("тайм-аут сборки SETP")
pci_type = data[0] & 0xF0
if pci_type == 0x10:
if len(data) != 8:
raise ValueError("первый сегмент должен иметь DLC 8")
total = int.from_bytes(data[1:3], "little")
if not 18 <= total <= 530:
raise ValueError("неверный размер SETP")
self.can_id = can_id
self.total = total
self.data = bytearray(data[3:])
self.next_sequence = 1
self.deadline_ms = now_ms + self.timeout_ms
return None
if pci_type == 0x20:
sequence = data[0] & 0x0F
if (
self.can_id is None
or can_id != self.can_id
or sequence != self.next_sequence
or len(data) < 2
):
self.reset()
raise ValueError("ошибка последовательности CAN-сегментов")
if len(data) - 1 > self.total - len(self.data):
self.reset()
raise ValueError("лишние байты CAN-сегмента")
self.data.extend(data[1:])
self.next_sequence = (self.next_sequence + 1) & 0x0F
self.deadline_ms = now_ms + self.timeout_ms
if len(self.data) == self.total:
packet = bytes(self.data)
packet_can_id = self.can_id
self.reset()
return parse_setp(packet, packet_can_id)
return None
if pci_type == 0x30:
return {"flow_control": data[0] & 0x0F, "data": data[1:]}
raise ValueError("неизвестный PCI")
В SETGUI эти операции уже реализованы в
third_party/templates/python/setprotocol/can.py; собственный parser нужен
только внешнему анализатору или диагностическому скрипту.
Для используемых сейчас адресов достаточно следующих признаков:
0x12, PCI начинается с 0x10, 0x2N
или 0x3S, после reassembly присутствует A5 5A 02;MessageType в битах 19..16 равен 0x9..0xD, каждый кадр разбирается
самостоятельно.Однако универсальное автоопределение только по одному CAN ID невозможно:
комбинация Priority/Route/DeviceType v1 теоретически тоже может дать верхнее
поле 0x12, а первый байт firmware data v1 может случайно совпасть с PCI.
Надёжный анализатор должен учитывать настроенный режим узла либо подтвердить v2
только после сборки кадра с корректными A5 5A 02, длиной и CRC32.
third_party/templates/c/protocan-boot/src/pcan_boot.c;third_party/templates/c/set-protocol/src/set_can.c;third_party/templates/c/set-protocol/src/set_protocol.c;third_party/templates/c/set-protocol/src/set_firmware.c;third_party/templates/python/setprotocol/can.py.Этот раздел генерируется из канонического c/set-protocol/docs/SETPROTOCOL.md. Редактировать нужно Markdown, затем запускать doc/build-setprotocol-html.ps1.
SETProtocol — общее C99-ядро для SETGUI, Android GUI, прошивок и утилит.
Оно объединяет основной SET protocol v2 и поддерживаемые форматы переходного
периода: ProtoCAN bridge и SETGUI transport v1. Windows, Linux, Android и
микроконтроллер используют одинаковые правила кадра, CRC, адресации,
телеметрии, обновления и потокового разбора.
Ядро не открывает COM-порт, CAN-адаптер или сокет. COM, SLCAN, SocketCAN, USB CDC, TCP и аппаратный CAN относятся к портам. Они доставляют байты или CAN-кадры, а SETProtocol проверяет и интерпретирует их одинаково на всех платформах.
SETGUI / Android GUI / CLI / firmware
│ прикладные команды и события
▼
Python facade / JNI / прямой C API
│ стабильный ABI или C99 API
▼
┌──────────────────────── SETProtocol ────────────────────────┐
│ SET v2 │ ProtoCAN ID │ v1 parsers │ CRC │ GAS │ telemetry │
└─────────────────────────────────────────────────────────────┘
│ байты или нормализованный CAN frame
▼
COM │ SLCAN │ SocketCAN │ USB CDC │ TCP │ STM32 UART/CAN
В ядре находятся:
ctypes, JNI и будущего Swift/FFI.За пределами ядра остаются:
COM6, can0 или Bluetooth/USB endpoint;Отсюда следует важное правило: 500000 на экране COM — это baud rate последовательного моста, а 500 kbit/s в CAN-настройках — bitrate самой CAN-шины. Ядро не подменяет одно другим и не выбирает эти значения автоматически.
| Модуль | Роль | Платформенные зависимости |
|---|---|---|
set_protocol |
SET v2 frame, CRC32, stream/datagram parser | нет |
set_can |
CAN segmentation, flow control и reassembly | доставка CAN frame и время |
set_telemetry |
подписки и типизированные PUBLISH-пакеты | часы/callbacks приложения |
set_firmware |
BEGIN/DATA/END/STATUS и resume state machine | Flash/verify/reboot backend |
pcan_id |
Упаковка/разбор 29-битного ProtoCAN ID | нет |
pcan_crc |
CRC-16/CCITT-FALSE | нет |
pcan_frame |
Формат AA 55, encode и потоковый parser |
нет |
gui_frame |
Формат A5 5A, CRC32, encode, parser и link |
нет |
pcan_link |
Экземпляр канала, SEQ, RX/TX и статистика | два callback порта |
pcan_ring |
SPSC-кольцо и непрерывный участок для DMA | нет |
pcan_gas |
Карта 16-битных регистров и bridge к кадрам | callbacks региона |
gui_catalog |
C-каталог публикуемых GUI-полей | нет; входит в общий shared build |
pcan_abi |
Экспорт скалярного ABI для FFI | ABI компилятора C |
Общая точка включения для C-кода — include/setprotocol.h.
Иностранные runtimes должны использовать include/setprotocol_abi.h, а не
повторять внутреннюю раскладку pcan_parser_t или gui_parser_t.
Shared-библиотека setprotocol содержит SET v2 и совместимые legacy-модули.
ABI v1 пока экспортирует функции pcan_abi_*: имена намеренно сохранены для
бинарной совместимости SETGUI/Android. Расширение ABI для прямого SET v2 FFI
должно быть совместимым добавлением или новой версией ABI.
SETProtocol поддерживает основной v2 и два legacy-формата. После первого корректного ответа формат соединения фиксируется до отключения.
AA 55AA 55 | LEN | SEQ | FLAGS | CAN_ID[4] LE | DATA[0..8] | CRC16 LE
LEN = 6 + DLC, поэтому допустимый диапазон — 6..14. CRC-16/CCITT-FALSE
считается по участку от LEN до последнего байта DATA. Максимальный размер
кадра — 19 байт. Формат переносит один classic CAN 2.0 кадр через COM, USB CDC,
RS-232, RS-485 или TCP byte stream.
Флаги:
| Бит | Имя | Значение |
|---|---|---|
| 0 | IDE |
расширенный 29-битный CAN ID |
| 1 | RTR |
remote frame |
| 2 | DIR |
0 из CAN в host, 1 из host в CAN |
| 3 | ERR |
служебный кадр диагностики моста |
A5 5AA5 5A | VER | TYPE | SEQ[2] BE | SIZE[2] BE | PAYLOAD[0..512] | CRC32 LE
Версия сейчас равна 1. Заголовочные SEQ и SIZE идут big-endian, CRC32
IEEE — little-endian. Payload до 512 байт нужен для каталога, чтения/записи
регистров, диагностики и потока значений. Это не CAN-кадр и у него нет DLC.
Оба parser принимают произвольные chunks: один вызов может содержать половину
кадра, несколько кадров или мусор между ними. Границы read() не считаются
границами протокольных сообщений.
A5 5A 02A5 5A | VER=02 | FLAGS | TYPE u16 LE | SOURCE u16 LE | DEST u16 LE |
SEQ u16 LE | SIZE u16 LE | PAYLOAD[0..512] | CRC32 LE
Это основной формат новых устройств. Он одинаков поверх RS-232/485, USB CDC,
TCP и UDP; CAN переносит байты полного v2-кадра через сегментацию. В v2
объединены запросы/ответы, события телеметрии и firmware flow. Нормативный
контракт находится в PROTOCOL.md.
28 27 26..24 23..20 19..16 15..0
Priority | Route | DeviceType | DeviceID | MsgType | MsgBody
Для переносимости используются маски и сдвиги, а не C bit-fields. ABI-функции
pcan_abi_id_pack() и pcan_abi_id_unpack() дают одинаковую раскладку при
MSVC, GCC и Clang.
pcan_abi.h экспортирует простые числа, указатели и явно ограниченные буферы.
Текущая версия возвращается pcan_abi_version() и равна 1.
| Функция | Назначение |
|---|---|
pcan_abi_version |
Проверить совместимость загруженной библиотеки |
pcan_abi_id_pack/unpack |
Преобразовать поля ProtoCAN ID |
pcan_abi_crc16 |
Рассчитать CRC-16/CCITT-FALSE |
pcan_abi_frame_encode |
Собрать целый AA55 кадр |
pcan_abi_parser_size |
Узнать размер opaque parser context |
pcan_abi_parser_init |
Инициализировать память, принадлежащую вызывающему |
pcan_abi_parser_push |
Передать один байт; 1 означает готовый кадр |
pcan_abi_parser_stats |
Получить frames/CRC/bad length/stray bytes |
| Функция | Назначение |
|---|---|
pcan_abi_gui_crc32 |
Рассчитать CRC32 IEEE |
pcan_abi_gui_frame_encode |
Собрать целый A55A кадр |
pcan_abi_gui_parser_size/init/push |
Управлять opaque GUI parser context |
pcan_abi_gui_parser_stats |
Получить frames/CRC/version/length/stray bytes |
Возврат 0 из encode означает неверные аргументы или недостаточный output
buffer. Parser API возвращает отрицательное значение при неверном context,
0 пока кадр не собран и 1 при готовом кадре.
В переносимом C-слое нет malloc, singleton и скрытого глобального parser.
Каждый канал имеет собственное состояние. В ABI вызывающий сначала спрашивает
его размер, выделяет байтовый блок и передаёт его в init.
size_t size = pcan_abi_parser_size();
void *storage = /* память вызывающей стороны размером size */;
pcan_abi_parser_init(storage, size);
Это позволяет:
ctypes.create_string_buffer() в Python;Один parser context нельзя одновременно изменять из нескольких потоков. Правильная модель — один владелец на канал или внешняя блокировка. Кольцевой буфер рассчитан на одного писателя и одного читателя (SPSC). Для нескольких писателей синхронизацию обеспечивает порт/приложение.
| Среда | Артефакт | Состояние |
|---|---|---|
| Windows desktop | setprotocol.dll + Python ctypes |
используется SETGUI, проверено тестами |
| Android | libsetprotocol.so + JNI + Kotlin facade |
сборка ABI arm64-v8a, armeabi-v7a, x86, x86_64 проверяется Android build |
| Linux desktop | libsetprotocol.so + тот же ABI |
ядро и сборщик готовы; нужен Linux CI/smoke-test приложения |
| macOS | libsetprotocol.dylib + тот же ABI |
исходники совместимы; отдельная упаковка не проверена |
| STM32F4 | прямой C99 + UART/DMA port | готовый порт в ports/stm32f4 |
| Другой MCU | прямой C99 | реализуются только callbacks I/O/времени/памяти |
| iOS/Swift | C ABI | ABI подходит, Swift wrapper пока не добавлен |
Само ядро не содержит WinAPI, поэтому собирается GCC или Clang. Для SETGUI под
Linux остаются две отдельные задачи: упаковать libsetprotocol.so с приложением и
подключить нужный физический backend (pyserial для USB-COM или SocketCAN для
can0). Правила кадра, CRC и ID менять не потребуется.
SLCAN и SocketCAN — порты снифера, а не новая реализация протокола:
SLCAN text / struct can_frame
│ adapter
▼
can_id + flags + data
│
▼
общий decoder/UI
cmake -S c/set-protocol -B build/setprotocol -DSETP_BUILD_TESTS=ON
cmake --build build/setprotocol --config Release
ctest --test-dir build/setprotocol -C Release --output-on-failure
Результат shared-сборки называется setprotocol.dll, libsetprotocol.so или
libsetprotocol.dylib. Статическая цель называется setprotocol_static;
совместимое имя CMake-цели SET v2 — set_protocol.
python c/set-protocol/tools/build_host.py --output native/setprotocol.dll
python3 c/set-protocol/tools/build_host.py --output native/libsetprotocol.so
На Windows tool использует MSVC, на Unix ищет cc, clang или gcc.
from protocan.native import NativeProtocol
core = NativeProtocol()
raw = core.encode(sequence=1, flags=1, can_id=0x1234567, data=b"\xAA\xBB")
frames = core.parser().feed(raw)
Если библиотека лежит вне стандартного дерева:
export SETPROTOCOL_LIBRARY=/opt/set/lib/libsetprotocol.so
В PowerShell:
$env:SETPROTOCOL_LIBRARY = 'C:\set\native\setprotocol.dll'
ports/android/Android.mk компилирует те же C-файлы. Kotlin-класс
ru.setcorp.setprotocol.NativeSetProtocol отвечает только за удобный API, а JNI — за
преобразование типов и время жизни parser handle.
Добавьте нужные src/*.c и каталог include/ в проект. Порт STM32F4 не входит
автоматически в host CMake, потому что ему нужен CMSIS. Для другой платы
реализуйте pcan_io_t.write и pcan_io_t.tx_space; ISR/DMA лишь складывает
байты, а pcan_link_feed() вызывается в безопасном контексте приложения.
#include "pcan_abi.h"
uint8_t output[19];
const uint8_t data[] = {0xAA, 0xBB};
const uint8_t flags = 0x01U; /* IDE */
uint32_t id = pcan_abi_id_pack(1, 0, 2, 3, 4, 0x1234);
size_t written = pcan_abi_frame_encode(
7, flags, id, data, sizeof(data), output, sizeof(output));
Для firmware удобнее полный C API из protocan_transport.h: он даёт link,
callbacks, ring и GAS без FFI-обёртки.
| Симптом | Что проверить |
|---|---|
crc_errors растёт |
bitrate/baud, ground, termination, порядок байт, потерю chunks |
bad_len/length_errors |
выбран ли правильный формат AA55 или A55A |
version_errors |
версия GUI transport должна быть 1 |
много stray_bytes |
начало чтения посреди пакета допустимо; постоянный рост означает неверный порт |
| DLL/SO не найдена | путь, архитектуру процесса и SETPROTOCOL_LIBRARY |
Android UnsatisfiedLinkError |
имя setprotocol, ABI устройства и упаковку jniLibs/NDK |
| CAN пустой, но COM открыт | COM baud не равен CAN bitrate; проверьте настройку самого адаптера |
PCAN_ABI_VERSION.DLC <= 8; CAN FD не включён.GUI_RX_PAYLOAD_MAX можно уменьшить.protocan-boot.Минимальный quality gate:
ctest для test_transport и test_abi.tests/vectors/test-vectors.json.assembleDebug, если менялись ABI/JNI/Kotlin.Канонический код находится в templates/c/set-protocol. Проекты должны
получать его как Git submodule и фиксировать конкретный commit, а не хранить
разошедшиеся копии.