refactor: merge protocol cores as SETProtocol
This commit is contained in:
342
c/set-protocol/docs/SETPROTOCOL.md
Normal file
342
c/set-protocol/docs/SETPROTOCOL.md
Normal file
@@ -0,0 +1,342 @@
|
||||
# SETProtocol — переносимое протокольное ядро
|
||||
|
||||
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 проверяет и интерпретирует их одинаково на всех
|
||||
платформах.
|
||||
|
||||
## 1. Граница ответственности
|
||||
|
||||
```text
|
||||
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
|
||||
```
|
||||
|
||||
В ядре находятся:
|
||||
|
||||
- форматы проводных кадров и порядок байт;
|
||||
- SET protocol v2: команды, адресация, подписки и firmware state machines;
|
||||
- проверка длины, версии, DLC и контрольной суммы;
|
||||
- восстановление синхронизации после мусора или оборванного кадра;
|
||||
- упаковка и разбор ProtoCAN Extended ID;
|
||||
- счётчики качества входного потока;
|
||||
- общее адресное пространство регистров (GAS);
|
||||
- стабильная C ABI-граница для `ctypes`, JNI и будущего Swift/FFI.
|
||||
|
||||
За пределами ядра остаются:
|
||||
|
||||
- поиск устройств и выбор `COM6`, `can0` или Bluetooth/USB endpoint;
|
||||
- скорость UART и CAN bitrate;
|
||||
- драйверы SLCAN, SocketCAN, PCAN, CANable и vendor SDK;
|
||||
- разрешения Android USB и жизненный цикл приложения;
|
||||
- виджеты, вкладки, таблицы, графики и хранение настроек;
|
||||
- HAL, IRQ, DMA, RTOS, Flash и распиновка платы.
|
||||
|
||||
Отсюда следует важное правило: **500000 на экране COM — это baud rate
|
||||
последовательного моста, а 500 kbit/s в CAN-настройках — bitrate самой CAN-шины.
|
||||
Ядро не подменяет одно другим и не выбирает эти значения автоматически.**
|
||||
|
||||
## 2. Состав исходников
|
||||
|
||||
| Модуль | Роль | Платформенные зависимости |
|
||||
|---|---|---|
|
||||
| `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.
|
||||
|
||||
## 3. Три поддерживаемых wire format
|
||||
|
||||
SETProtocol поддерживает основной v2 и два legacy-формата. После первого
|
||||
корректного ответа формат соединения фиксируется до отключения.
|
||||
|
||||
### 3.1. CAN bridge: `AA 55`
|
||||
|
||||
```text
|
||||
AA 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` | служебный кадр диагностики моста |
|
||||
|
||||
### 3.2. GUI transport: `A5 5A`
|
||||
|
||||
```text
|
||||
A5 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()` не считаются
|
||||
границами протокольных сообщений.
|
||||
|
||||
### 3.3. SET protocol v2: `A5 5A 02`
|
||||
|
||||
```text
|
||||
A5 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`.
|
||||
|
||||
## 4. ProtoCAN Extended ID
|
||||
|
||||
```text
|
||||
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.
|
||||
|
||||
## 5. Стабильный ABI v1
|
||||
|
||||
`pcan_abi.h` экспортирует простые числа, указатели и явно ограниченные буферы.
|
||||
Текущая версия возвращается `pcan_abi_version()` и равна `1`.
|
||||
|
||||
### CAN bridge API
|
||||
|
||||
| Функция | Назначение |
|
||||
|---|---|
|
||||
| `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 |
|
||||
|
||||
### GUI API
|
||||
|
||||
| Функция | Назначение |
|
||||
|---|---|
|
||||
| `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` при готовом кадре.
|
||||
|
||||
## 6. Память, состояние и многопоточность
|
||||
|
||||
В переносимом C-слое нет `malloc`, singleton и скрытого глобального parser.
|
||||
Каждый канал имеет собственное состояние. В ABI вызывающий сначала спрашивает
|
||||
его размер, выделяет байтовый блок и передаёт его в `init`.
|
||||
|
||||
```c
|
||||
size_t size = pcan_abi_parser_size();
|
||||
void *storage = /* память вызывающей стороны размером size */;
|
||||
pcan_abi_parser_init(storage, size);
|
||||
```
|
||||
|
||||
Это позволяет:
|
||||
|
||||
- держать память статически на MCU;
|
||||
- использовать `ctypes.create_string_buffer()` в Python;
|
||||
- выделять handle только в JNI-адаптере;
|
||||
- одновременно разбирать несколько независимых линий.
|
||||
|
||||
Один parser context нельзя одновременно изменять из нескольких потоков.
|
||||
Правильная модель — один владелец на канал или внешняя блокировка. Кольцевой
|
||||
буфер рассчитан на одного писателя и одного читателя (SPSC). Для нескольких
|
||||
писателей синхронизацию обеспечивает порт/приложение.
|
||||
|
||||
## 7. Порты и адаптеры
|
||||
|
||||
| Среда | Артефакт | Состояние |
|
||||
|---|---|---|
|
||||
| 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 пока не добавлен |
|
||||
|
||||
### Linux
|
||||
|
||||
Само ядро не содержит WinAPI, поэтому собирается GCC или Clang. Для SETGUI под
|
||||
Linux остаются две отдельные задачи: упаковать `libsetprotocol.so` с приложением и
|
||||
подключить нужный физический backend (`pyserial` для USB-COM или SocketCAN для
|
||||
`can0`). Правила кадра, CRC и ID менять не потребуется.
|
||||
|
||||
SLCAN и SocketCAN — **порты снифера**, а не новая реализация протокола:
|
||||
|
||||
```text
|
||||
SLCAN text / struct can_frame
|
||||
│ adapter
|
||||
▼
|
||||
can_id + flags + data
|
||||
│
|
||||
▼
|
||||
общий decoder/UI
|
||||
```
|
||||
|
||||
## 8. Сборка
|
||||
|
||||
### CMake: Windows, Linux, macOS
|
||||
|
||||
```bash
|
||||
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`.
|
||||
|
||||
### Упрощённая host-сборка
|
||||
|
||||
```powershell
|
||||
python c/set-protocol/tools/build_host.py --output native/setprotocol.dll
|
||||
```
|
||||
|
||||
```bash
|
||||
python3 c/set-protocol/tools/build_host.py --output native/libsetprotocol.so
|
||||
```
|
||||
|
||||
На Windows tool использует MSVC, на Unix ищет `cc`, `clang` или `gcc`.
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
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)
|
||||
```
|
||||
|
||||
Если библиотека лежит вне стандартного дерева:
|
||||
|
||||
```bash
|
||||
export SETPROTOCOL_LIBRARY=/opt/set/lib/libsetprotocol.so
|
||||
```
|
||||
|
||||
В PowerShell:
|
||||
|
||||
```powershell
|
||||
$env:SETPROTOCOL_LIBRARY = 'C:\set\native\setprotocol.dll'
|
||||
```
|
||||
|
||||
### Android
|
||||
|
||||
`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()` вызывается в безопасном контексте приложения.
|
||||
|
||||
## 9. Пример прямого ABI
|
||||
|
||||
```c
|
||||
#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-обёртки.
|
||||
|
||||
## 10. Диагностика
|
||||
|
||||
| Симптом | Что проверить |
|
||||
|---|---|
|
||||
| `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; проверьте настройку самого адаптера |
|
||||
|
||||
## 11. Совместимость и ограничения
|
||||
|
||||
- ABI v1 изменяется только совместимым добавлением функций. Ломающее изменение
|
||||
требует нового значения `PCAN_ABI_VERSION`.
|
||||
- Wire format нельзя менять без версии/миграционного документа и тестовых
|
||||
векторов для C, Python и Android.
|
||||
- CAN bridge сейчас рассчитан на classic CAN: `DLC <= 8`; CAN FD не включён.
|
||||
- GUI payload ограничен 512 байтами; на MCU `GUI_RX_PAYLOAD_MAX` можно уменьшить.
|
||||
- В ядре нет готового SocketCAN/SLCAN/vendor backend: это следующий слой портов.
|
||||
- В ядро не входят виджеты GUI, настройки COM/CAN и обновление прошивки целиком.
|
||||
Для firmware flow существует отдельная библиотека `protocan-boot`.
|
||||
|
||||
## 12. Проверка изменений
|
||||
|
||||
Минимальный quality gate:
|
||||
|
||||
1. CMake build и `ctest` для `test_transport` и `test_abi`.
|
||||
2. Сверка машинных векторов `tests/vectors/test-vectors.json`.
|
||||
3. Python-тесты с обязательной загрузкой native core.
|
||||
4. Android unit tests и `assembleDebug`, если менялись ABI/JNI/Kotlin.
|
||||
5. Smoke-test целевого порта на реальной линии.
|
||||
|
||||
Канонический код находится в `templates/c/set-protocol`. Проекты должны
|
||||
получать его как Git submodule и фиксировать конкретный commit, а не хранить
|
||||
разошедшиеся копии.
|
||||
21
c/set-protocol/docs/legacy/CHANGELOG.md
Normal file
21
c/set-protocol/docs/legacy/CHANGELOG.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# История изменений ProtoCAN
|
||||
|
||||
Формат основан на Keep a Changelog. Версия относится к спецификации, а не к
|
||||
версии прошивки отдельного прибора.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Структурированный комплект документации `doc/protocan`.
|
||||
- Загрузочный сервис `MsgType=0x9…0xD`.
|
||||
- Два логических слота по 512 КиБ, 65 536 блоков по 8 байт каждый.
|
||||
- Машинные эталоны CAN ID в `examples/test-vectors.json`.
|
||||
|
||||
## [1.0] — 2026-08-29
|
||||
|
||||
### Added
|
||||
|
||||
- Зафиксирована 29-битная структура ProtoCAN ID.
|
||||
- Зафиксирована адресация 8 типов по 16 экземпляров.
|
||||
- Существующие сообщения `0x0…0x8`, `0xE`, `0xF` сохранены.
|
||||
78
c/set-protocol/docs/legacy/FRAME.md
Normal file
78
c/set-protocol/docs/legacy/FRAME.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Транспортный кадр
|
||||
|
||||
## Формат
|
||||
|
||||
```
|
||||
+------+------+-----+-----+-------+----------------+-------------+-------+-------+
|
||||
| 0xAA | 0x55 | LEN | SEQ | FLAGS | ID0 ID1 ID2 ID3| DATA[0..8] | CRC_L | CRC_H |
|
||||
+------+------+-----+-----+-------+----------------+-------------+-------+-------+
|
||||
```
|
||||
|
||||
| Поле | Байт | Описание |
|
||||
|---|---:|---|
|
||||
| SOF | 2 | сигнатура `0xAA 0x55` |
|
||||
| `LEN` | 1 | длина участка `SEQ..DATA` = `6 + DLC`, диапазон 6..14 |
|
||||
| `SEQ` | 1 | счётчик кадров 0..255, инкремент на каждый успешно отданный кадр |
|
||||
| `FLAGS` | 1 | см. ниже |
|
||||
| `ID` | 4 | 29-битный CAN-идентификатор, little-endian (старшие 3 бита = 0) |
|
||||
| `DATA` | 0..8 | `DLC = LEN - 6` байт данных CAN |
|
||||
| `CRC` | 2 | CRC-16/CCITT-FALSE, little-endian |
|
||||
|
||||
CRC считается по байтам от `LEN` до последнего байта `DATA` включительно;
|
||||
сигнатура SOF в расчёт не входит. Полином `0x1021`, начальное значение
|
||||
`0xFFFF`, без рефлексии и без финального XOR — контрольное значение для
|
||||
строки `123456789` равно `0x29B1`.
|
||||
|
||||
Максимальный размер кадра — 19 байт (`PCAN_FRAME_MAX`).
|
||||
|
||||
## FLAGS
|
||||
|
||||
| Бит | Имя | Значение |
|
||||
|---:|---|---|
|
||||
| 0 | `IDE` | 1 = расширенный ID (29 бит), 0 = стандартный (11 бит) |
|
||||
| 1 | `RTR` | 1 = remote frame |
|
||||
| 2 | `DIR` | 0 = кадр пришёл из CAN, 1 = кадр надо передать в CAN |
|
||||
| 3 | `ERR` | 1 = служебный кадр моста (диагностика), не трафик шины |
|
||||
| 7..4 | — | резерв, передавать нулями |
|
||||
|
||||
## Синхронизация
|
||||
|
||||
Приёмник ищет `0xAA 0x55`, читает `LEN`, проверяет диапазон `6..14`,
|
||||
набирает `LEN + 2` байт и сверяет CRC. При неверном `LEN` или несовпадении
|
||||
CRC разборщик возвращается к поиску сигнатуры, причём байт, оборвавший
|
||||
разбор, сам проверяется на `0xAA` — последовательность `AA AA 55` тоже
|
||||
распознаётся. Потеря синхронизации стоит не больше одного кадра.
|
||||
|
||||
Счётчики разбора (`pcan_parse_stats_t`) отдельно считают кадры, ошибки CRC,
|
||||
неверные `LEN` и байты вне кадров — по ним видно, шумит линия или сбоит
|
||||
источник.
|
||||
|
||||
## SEQ
|
||||
|
||||
`SEQ` инкрементируется только на успешно отданном кадре: если кадр не влез
|
||||
в очередь передачи, счётчик не двигается, и приёмник не засчитает потерю
|
||||
там, где кадра просто не было. Разрыв в `SEQ` на приёме означает реальную
|
||||
потерю в линии.
|
||||
|
||||
## Почему так
|
||||
|
||||
- **Длина плюс CRC, без байт-стаффинга.** Стаффинг раздувает кадр
|
||||
непредсказуемо и усложняет расчёт таймингов на полудуплексной линии.
|
||||
Фиксированный заголовок даёт заранее известный максимум 19 байт.
|
||||
- **Сигнатура из двух байт.** Один байт слишком часто встречается в
|
||||
случайных данных; два дают приемлемую вероятность ложного старта,
|
||||
который всё равно отсеет CRC.
|
||||
- **`LEN` в начале.** Приёмник сразу знает, сколько байт набирать, и не
|
||||
зависит от содержимого данных.
|
||||
- **Little-endian везде.** Совпадает с порядком регистров в
|
||||
`PROTOCAN_SEND_GENERAL_ADDRESS_SPACE()` и с обоими целевыми МК.
|
||||
|
||||
## Пример
|
||||
|
||||
CAN-кадр: ID `0x1234567`, DLC 2, данные `AA BB`, `SEQ = 1`, флаг `IDE`.
|
||||
|
||||
```
|
||||
AA 55 08 01 01 67 45 23 01 AA BB FE 14
|
||||
```
|
||||
|
||||
`LEN = 6 + 2 = 8`, `FLAGS = 0x01`, CRC = `0x14FE`.
|
||||
87
c/set-protocol/docs/legacy/GAS.md
Normal file
87
c/set-protocol/docs/legacy/GAS.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Общее адресное пространство (GAS)
|
||||
|
||||
Плоское пространство 16-битных регистров с адресом `0x0000..0xFFFF`.
|
||||
Пространство собирается из **регионов**; регионы не перекрываются и
|
||||
хранятся отсортированными по адресу, поиск — двоичный.
|
||||
|
||||
## Регион
|
||||
|
||||
```c
|
||||
typedef struct pcan_gas_region {
|
||||
uint16_t base; /* адрес первого регистра */
|
||||
uint16_t count; /* число регистров */
|
||||
uint16_t *storage; /* массив либо NULL */
|
||||
pcan_gas_read_fn read;
|
||||
pcan_gas_write_fn write;
|
||||
uint8_t flags; /* PCAN_GAS_RDONLY / PCAN_GAS_WRONLY */
|
||||
void *user;
|
||||
const char *name;
|
||||
} pcan_gas_region_t;
|
||||
```
|
||||
|
||||
Если `storage != NULL`, чтение и запись идут прямо в массив — это самый
|
||||
дешёвый вариант для обычных уставок. Если нужен вычисляемый регистр
|
||||
(счётчик, состояние периферии, время работы), задайте `read`/`write`:
|
||||
колбэк получает смещение внутри региона и указатель `user`.
|
||||
|
||||
`pcan_gas_map_validate()` проверяет карту на этапе старта: нулевые регионы,
|
||||
выход за `0xFFFF`, перекрытие, нарушение порядка и регион без источника
|
||||
данных. Вызывайте её один раз при инициализации — ошибка в таблице ловится
|
||||
сразу, а не через месяц в поле.
|
||||
|
||||
## Доступ
|
||||
|
||||
```c
|
||||
uint16_t v;
|
||||
pcan_gas_read(&map, 0x0002, &v);
|
||||
pcan_gas_write(&map, 0x0002, 0x1234);
|
||||
|
||||
uint16_t block[4];
|
||||
uint16_t n = pcan_gas_read_block(&map, 0x0000, block, 4);
|
||||
```
|
||||
|
||||
Блочное чтение обрывается на первом адресе, которого нет в карте, поэтому
|
||||
вызывающий всегда получает непрерывный кусок и знает его длину.
|
||||
|
||||
## Отображение на кадры ProtoCAN
|
||||
|
||||
Тип сообщения `PCAN_MSG_GAS` (`0b0011`). Адрес первого регистра лежит
|
||||
в `MsgBody`, данные — до 4 регистров подряд, младшим байтом вперёд.
|
||||
Это ровно то, что делает `PROTOCAN_SEND_GENERAL_ADDRESS_SPACE()`
|
||||
в `SETCAN/Src/protocan.c`, поэтому обмен совместим с существующими
|
||||
устройствами.
|
||||
|
||||
| Кадр | DLC | Смысл |
|
||||
|---|---:|---|
|
||||
| GAS, `MsgBody = addr` | 0 | **запрос на чтение** |
|
||||
| GAS, `MsgBody = addr` | 2..8 | значения регистров начиная с `addr` |
|
||||
|
||||
Запрос на чтение с `DLC = 0` — **расширение**: в исходном коде SETCAN
|
||||
такой кодировки нет, там ответы на GAS формировала заглушка `ProtoCanMsgToGeneralAddressSpace()`,
|
||||
возвращавшая строку `GAS-XXXX`. Кодировка выбрана так, чтобы не занимать
|
||||
новых типов сообщений и не конфликтовать с существующим форматом ответа.
|
||||
|
||||
```c
|
||||
pcan_frame_t rsp;
|
||||
if (pcan_gas_handle(&map, &incoming, &rsp)) {
|
||||
pcan_link_send(&link, &rsp); /* был запрос на чтение */
|
||||
}
|
||||
```
|
||||
|
||||
`pcan_gas_handle()`:
|
||||
|
||||
- на запрос чтения кладёт в `rsp` до 4 регистров и переключает `Route`
|
||||
на `FROM_DEVICE`, возвращает `true`;
|
||||
- на запись пишет регистры в карту и возвращает `false` — ответа нет;
|
||||
- если адреса нет в карте, возвращает `false`: отвечать нечем, а молчание
|
||||
честнее, чем ответ с нулями.
|
||||
|
||||
## Ограничения
|
||||
|
||||
- В один кадр помещается не больше 4 регистров (`PCAN_GAS_REGS_PER_FRAME`).
|
||||
Длинные блоки разбивайте на несколько кадров.
|
||||
- Частичная запись: если в середине блока попался адрес вне карты или
|
||||
регион только для чтения, запись обрывается на нём. `pcan_gas_write_block()`
|
||||
возвращает число фактически записанных регистров.
|
||||
- Атомарности между регистрами нет. Если два регистра обязаны меняться
|
||||
вместе, заведите колбэк, который применяет их по записи второго.
|
||||
158
c/set-protocol/docs/legacy/GUI_CATALOG.md
Normal file
158
c/set-protocol/docs/legacy/GUI_CATALOG.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Каталог общего адресного пространства и поток значений
|
||||
|
||||
Расширение GUI-протокола SETGUI (`A5 5A`, см. `gui_desktop/core/protocol.py`)
|
||||
для работы с общим адресным пространством: прибор сам объявляет, какие
|
||||
регистры у него есть и как они называются, а оператор выбирает, что
|
||||
показывать. Схема повторяет то, как устроен реестр регистров в
|
||||
ST Motor Control Workbench / Motor Pilot.
|
||||
|
||||
Транспорт не меняется: `A5 5A | ver | type | seq(BE) | size(BE) | payload | CRC32(LE)`,
|
||||
payload до 512 байт. Добавлены только три типа сообщений.
|
||||
|
||||
```
|
||||
прибор GUI
|
||||
│ ── GAS_CATALOG (seq = 0) ──────────► │ каталог приходит сам
|
||||
│ ── GAS_CATALOG (seq = 0) ──────────► │ при инициализации
|
||||
│ ── GAS_CATALOG (seq = 0) ──────────► │
|
||||
│ │ оператор отмечает нужное
|
||||
│ ◄──────────────── GAS_WATCH_SET ──── │
|
||||
│ ── GAS_WATCH_SET (эхо) ────────────► │
|
||||
│ ── GAS_WATCH_DATA (seq = 0) ───────► │ поток значений
|
||||
│ ── GAS_WATCH_DATA (seq = 0) ───────► │
|
||||
```
|
||||
|
||||
## Типы сообщений
|
||||
|
||||
Заняты из свободного диапазона `0x11..0x1F` (между `READ_LOGS = 0x10`
|
||||
и `SENSOR_SCAN = 0x20`). Существующие значения не перенумерованы.
|
||||
|
||||
| Код | Имя | Направление |
|
||||
|---:|---|---|
|
||||
| `0x11` | `GAS_CATALOG` | запрос GUI → прибор; записи прибор → GUI |
|
||||
| `0x12` | `GAS_WATCH_SET` | GUI → прибор, прибор отвечает эхом |
|
||||
| `0x13` | `GAS_WATCH_DATA` | прибор → GUI, без запроса, `sequence = 0` |
|
||||
|
||||
Незапрошенные кадры прибор публикует с `sequence = 0` — так же, как уже
|
||||
устроены `SENSOR_DATA` и `UI_STATE`.
|
||||
|
||||
## `GAS_CATALOG` (0x11)
|
||||
|
||||
### Запрос (GUI → прибор), 4 байта
|
||||
|
||||
| Смещение | Тип | Поле |
|
||||
|---:|---|---|
|
||||
| 0 | u16 LE | `start_index` — порядковый номер записи, **не адрес** |
|
||||
| 2 | u16 LE | `max_count` — сколько записей вернуть; 0 = сколько влезет |
|
||||
|
||||
### Ответ и автопубликация (прибор → GUI)
|
||||
|
||||
| Смещение | Тип | Поле |
|
||||
|---:|---|---|
|
||||
| 0 | u16 LE | `total` — всего записей в каталоге |
|
||||
| 2 | u16 LE | `start_index` — индекс первой записи в пакете |
|
||||
| 4 | u16 LE | `count` — записей в пакете |
|
||||
| 6 | | `count` записей по 32 байта |
|
||||
|
||||
Одна запись — 32 байта:
|
||||
|
||||
| Смещение | Тип | Поле |
|
||||
|---:|---|---|
|
||||
| 0 | u16 LE | `address` — адрес в общем адресном пространстве |
|
||||
| 2 | u8 | `type` — формат значения |
|
||||
| 3 | u8 | `flags` — доступ |
|
||||
| 4 | i8 | `scale_pow10` — значение = `raw * 10^scale` |
|
||||
| 5 | u8 | `unit` — код единицы измерения |
|
||||
| 6 | u16 LE | резерв, нули |
|
||||
| 8 | 24 байта | `name` — UTF-8, дополнено нулями |
|
||||
|
||||
При payload 512 байт в один пакет входит `(512 - 6) / 32 = 15` записей.
|
||||
|
||||
Под имя отведено 24 байта — это 12 кириллических символов в UTF-8.
|
||||
На 16 байтах не помещалось даже «Температура», поэтому поле шире,
|
||||
чем кажется нужным для латиницы.
|
||||
|
||||
### `type`
|
||||
|
||||
| Код | Значение | Регистров |
|
||||
|---:|---|---:|
|
||||
| 0 | `U16` — беззнаковое | 1 |
|
||||
| 1 | `I16` — знаковое | 1 |
|
||||
| 2 | `U32` — беззнаковое, младшее слово первым | 2 |
|
||||
| 3 | `I32` — знаковое, младшее слово первым | 2 |
|
||||
| 4 | `BITS` — битовое поле | 1 |
|
||||
|
||||
Многословные значения занимают подряд идущие адреса; в потоке они
|
||||
приходят отдельными словами, GUI собирает их сам.
|
||||
|
||||
### `flags`
|
||||
|
||||
| Бит | Смысл |
|
||||
|---:|---|
|
||||
| 0 | доступно на чтение |
|
||||
| 1 | доступно на запись |
|
||||
| 2 | включить в подписку по умолчанию |
|
||||
|
||||
### `unit`
|
||||
|
||||
| Код | Единица | Код | Единица |
|
||||
|---:|---|---:|---|
|
||||
| 0 | — | 6 | мс |
|
||||
| 1 | В | 7 | с |
|
||||
| 2 | А | 8 | кбит/с |
|
||||
| 3 | °C | 9 | шт. |
|
||||
| 4 | % | 10 | об/мин |
|
||||
| 5 | Гц | 11 | Вт |
|
||||
|
||||
## `GAS_WATCH_SET` (0x12)
|
||||
|
||||
### Запрос (GUI → прибор)
|
||||
|
||||
| Смещение | Тип | Поле |
|
||||
|---:|---|---|
|
||||
| 0 | u16 LE | `period_ms` — период потока; **0 останавливает поток** |
|
||||
| 2 | u16 LE | `count` — число адресов, не больше `GAS_WATCH_MAX` (64) |
|
||||
| 4 | | `count` × u16 LE — адреса в нужном порядке |
|
||||
|
||||
Порядок адресов сохраняется: значения в `GAS_WATCH_DATA` приходят
|
||||
ровно в том же порядке, без повторной передачи адресов.
|
||||
|
||||
### Ответ (прибор → GUI), 4 байта
|
||||
|
||||
| Смещение | Тип | Поле |
|
||||
|---:|---|---|
|
||||
| 0 | u16 LE | `period_ms` — период, который прибор реально установил |
|
||||
| 2 | u16 LE | `count` — сколько адресов принято |
|
||||
|
||||
Прибор может принять меньше, чем попросили: адрес вне карты в подписку
|
||||
не берётся. Расхождение `count` с запросом — сигнал GUI, что часть
|
||||
адресов отвергнута.
|
||||
|
||||
## `GAS_WATCH_DATA` (0x13)
|
||||
|
||||
Прибор → GUI, `sequence = 0`, без запроса.
|
||||
|
||||
| Смещение | Тип | Поле |
|
||||
|---:|---|---|
|
||||
| 0 | u32 LE | `timestamp_ms` — время прибора от старта |
|
||||
| 4 | u16 LE | `count` |
|
||||
| 6 | | `count` × u16 LE — значения в порядке подписки |
|
||||
|
||||
`timestamp_ms` берётся у прибора, а не у хоста: по нему видно реальный
|
||||
период и провалы, которые иначе замаскировала бы буферизация UART.
|
||||
|
||||
## Замечания по реализации
|
||||
|
||||
* **Каталог статичен.** Он описывает прошивку, а не состояние, поэтому
|
||||
публикуется один раз при инициализации периферии. GUI может перечитать
|
||||
его запросом в любой момент.
|
||||
* **Подписка живёт до переподключения.** Прибор не сохраняет её в
|
||||
энергонезависимой памяти: после сброса поток молчит, пока GUI не
|
||||
пришлёт `GAS_WATCH_SET` снова.
|
||||
* **Поток не должен забивать линию.** При периоде 10 мс и 64 адресах
|
||||
выходит 134 байта на пакет и 13.4 кбайт/с — половина пропускной
|
||||
способности 256000 бод. Прибор пропускает такт, если в очереди
|
||||
передачи нет места для целого пакета, и это видно по разрыву
|
||||
`timestamp_ms`.
|
||||
* **Значения передаются сырыми.** Пересчёт в физические величины делает
|
||||
GUI по `scale_pow10` и `unit` — прибор не тратит на это такты и не
|
||||
теряет точность на промежуточном округлении.
|
||||
56
c/set-protocol/docs/legacy/OAP.md
Normal file
56
c/set-protocol/docs/legacy/OAP.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Общее адресное пространство
|
||||
|
||||
Статус: **Stable, данные ведутся в XLSX**
|
||||
Порядок значений: **16-битные регистры, little-endian в CAN payload**
|
||||
|
||||
Редактируемый источник реестра:
|
||||
[`Протокол CAN и ОАП.xlsx`](../../Протокол%20CAN%20и%20ОАП.xlsx).
|
||||
|
||||
Просматриваемая большая таблица находится в
|
||||
[`Протокол CAN и ОАП.html`](../../Протокол%20CAN%20и%20ОАП.html) и
|
||||
[`Протокол CAN и ОАП.md`](../../Протокол%20CAN%20и%20ОАП.md).
|
||||
|
||||
## Назначение
|
||||
|
||||
ОАП связывает 16-битный адрес регистра с его типом, назначением, доступом и
|
||||
масштабом. В ProtoCAN используется `MsgType=0x3`, а `MsgBody` содержит адрес
|
||||
первого регистра.
|
||||
|
||||
## Обязательные поля реестра
|
||||
|
||||
| Поле | Требование |
|
||||
|---|---|
|
||||
| AddressHex | `0x0000…0xFFFF`, уникальное значение |
|
||||
| AddressDec | десятичный эквивалент AddressHex |
|
||||
| Group | функциональная группа |
|
||||
| Name | однозначное имя параметра |
|
||||
| Type | `u16`, `i16`, `u32`, `i32`, `float32`, bitmap или массив |
|
||||
| Registers | число занятых 16-битных регистров |
|
||||
| Access | `R`, `W` или `RW` |
|
||||
| Unit | физическая единица либо `—` |
|
||||
| Scale | множитель/делитель представления |
|
||||
| Default | значение после сброса, если применимо |
|
||||
| Description | семантика, диапазон и особые значения |
|
||||
|
||||
## Правила ведения
|
||||
|
||||
- Адрес не переиспользуется с другим смыслом после выпуска стабильной версии.
|
||||
- Многорегистровое значение занимает непрерывный диапазон.
|
||||
- Порядок 16-битных слов для 32-битного значения фиксируется в строке типа.
|
||||
- Резервные диапазоны явно отмечаются и не используются без изменения версии.
|
||||
- Удалённый параметр помечается deprecated, а не исчезает молча.
|
||||
- Изменение адреса, типа или масштаба отражается в `CHANGELOG.md`.
|
||||
|
||||
## Экспорт
|
||||
|
||||
Для программной генерации каталог следует экспортировать из XLSX в CSV с
|
||||
UTF-8 и фиксированными английскими именами колонок. CSV должен проверяться на:
|
||||
|
||||
- уникальность адресов;
|
||||
- пересечение многорегистровых значений;
|
||||
- допустимые типы и права доступа;
|
||||
- равенство шестнадцатеричного и десятичного адреса;
|
||||
- попадание адреса в диапазон `0x0000…0xFFFF`.
|
||||
|
||||
До появления автоматического экспортёра нормативным источником адресов
|
||||
остаётся XLSX, а HTML/Markdown считаются представлением.
|
||||
114
c/set-protocol/docs/legacy/PROTOCOL.md
Normal file
114
c/set-protocol/docs/legacy/PROTOCOL.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# ProtoCAN — базовый протокол
|
||||
|
||||
Статус: **Stable с зарезервированным загрузочным расширением**
|
||||
Версия: **1.0**
|
||||
Порядок байтов payload: **little-endian**, если явно не указано иное
|
||||
|
||||
## Назначение
|
||||
|
||||
ProtoCAN — прикладной протокол поверх classic CAN 2.0B. Используются только
|
||||
расширенные 29-битные идентификаторы (`IDE=1`) и payload длиной 0…8 байт.
|
||||
|
||||
## Термины
|
||||
|
||||
| Термин | Значение |
|
||||
|---|---|
|
||||
| ПМ | управляющий модуль |
|
||||
| прибор | адресуемый узел на шине |
|
||||
| `DeviceType` | тип прибора, 0…7 |
|
||||
| `DeviceID` | экземпляр прибора данного типа, 0…15 |
|
||||
| `MsgType` | класс сообщения или сервис |
|
||||
| `MsgBody` | 16-битное поле, формат которого зависит от `MsgType` |
|
||||
|
||||
Пара `DeviceType/DeviceID` задаёт до `8 × 16 = 128` уникальных адресов.
|
||||
|
||||
## Расширенный CAN ID
|
||||
|
||||
```text
|
||||
28 27 26...24 23...20 19...16 15........0
|
||||
Priority Route DeviceType DeviceID MsgType MsgBody
|
||||
1 бит 1 бит 3 бита 4 бита 4 бита 16 бит
|
||||
```
|
||||
|
||||
```c
|
||||
can_id =
|
||||
((uint32_t)priority << 28) |
|
||||
((uint32_t)route << 27) |
|
||||
((uint32_t)device_type << 24) |
|
||||
((uint32_t)device_id << 20) |
|
||||
((uint32_t)msg_type << 16) |
|
||||
msg_body;
|
||||
```
|
||||
|
||||
| Поле | Значения | Назначение |
|
||||
|---|---|---|
|
||||
| `Priority` | `0` critical, `1` standard | CAN-арбитраж |
|
||||
| `Route` | `0` от ПМ, `1` от прибора | логическое направление |
|
||||
| `DeviceType` | `0…7` | тип прибора |
|
||||
| `DeviceID` | `0…15` | номер экземпляра |
|
||||
| `MsgType` | `0…15` | тип сообщения |
|
||||
| `MsgBody` | `0…65535` | команда, адрес или номер блока |
|
||||
|
||||
`Route` не является направлением физического трансивера. Ответ прибора
|
||||
сохраняет адрес `DeviceType/DeviceID` и устанавливает `Route=1`.
|
||||
|
||||
## Реестр `MsgType`
|
||||
|
||||
| Код | Имя | Основное направление | DLC | Статус |
|
||||
|---:|---|---|---:|---|
|
||||
| `0x0` | `BROADCAST` | ПМ → все | зависит от команды | stable |
|
||||
| `0x1` | `DISCRETE` | оба | 0…8 | stable |
|
||||
| `0x2` | `ANALOG` | оба | 0…8 | stable |
|
||||
| `0x3` | `GAS` | оба | 0/2/4/6/8 | stable |
|
||||
| `0x4` | `MODBUS_COIL` | оба | 0…8 | stable |
|
||||
| `0x5` | `MODBUS_DISCRETE` | оба | 0…8 | stable |
|
||||
| `0x6` | `MODBUS_HOLDING` | оба | 0…8 | stable |
|
||||
| `0x7` | `MODBUS_INPUT` | оба | 0…8 | stable |
|
||||
| `0x8` | `ERROR` | прибор → ПМ | 0 | stable |
|
||||
| `0x9` | `BOOT_CONTROL` | ПМ → прибор | 0/8 | draft |
|
||||
| `0xA` | `BOOT_DATA_A` | ПМ → прибор | 8 | draft |
|
||||
| `0xB` | `BOOT_DATA_B` | ПМ → прибор | 8 | draft |
|
||||
| `0xC` | `BOOT_STATUS` | прибор → ПМ | 8 | draft |
|
||||
| `0xD` | `BOOT_DISCOVERY` | прибор → ПМ | 8 | draft |
|
||||
| `0xE` | `SETTINGS` | оба | 0/1/8 | stable |
|
||||
| `0xF` | `PULSE` | прибор → сеть | 1 | stable |
|
||||
|
||||
Подробный формат `0x9…0xD` находится в [BOOTLOADER.md](BOOTLOADER.md).
|
||||
|
||||
## Разметки `MsgBody`
|
||||
|
||||
| `MsgType` | Биты `MsgBody` |
|
||||
|---|---|
|
||||
| broadcast | команда `[15:4]`, параметр `[3:0]` |
|
||||
| discrete/analog | подтип `[15:12]`, значение/адрес `[11:0]` |
|
||||
| Modbus | начальный адрес `[15:4]`, количество `[3:0]` |
|
||||
| GAS | адрес первого 16-битного регистра `[15:0]` |
|
||||
| error | дополнительная информация `[15:8]`, код `[7:0]` |
|
||||
| settings | номер сборки `[15:8]`, позиция `[7:0]` |
|
||||
| boot control/status | `SessionID[15:8]`, команда `[7:0]` |
|
||||
| boot data | `BlockIndex[15:0]` |
|
||||
|
||||
## Общие правила обмена
|
||||
|
||||
- Многобайтовые значения в `DATA` передаются little-endian.
|
||||
- Узел игнорирует адресованные кадры с чужим `DeviceType/DeviceID`.
|
||||
- Прибор принимает команды ПМ с `Route=0`; ПМ принимает ответы с `Route=1`.
|
||||
- Стандартные 11-битные CAN ID не являются кадрами ProtoCAN.
|
||||
- RTR для загрузочного сервиса запрещён.
|
||||
- Неописанные комбинации `MsgType/MsgBody/DLC` должны отвергаться.
|
||||
|
||||
## Эталон упаковки ID
|
||||
|
||||
```text
|
||||
Priority = 1
|
||||
Route = 0
|
||||
DeviceType = 3
|
||||
DeviceID = 5
|
||||
MsgType = 0x9
|
||||
MsgBody = 0x0702
|
||||
|
||||
CAN ID = 0x13590702
|
||||
```
|
||||
|
||||
Этот пример соответствует `ENTER_BOOT`, `SessionID=7`. Машинные варианты
|
||||
находятся в [examples/test-vectors.json](examples/test-vectors.json).
|
||||
Reference in New Issue
Block a user