feat(protocan-transport): транспортный уровень ProtoCAN и каталог GUI
Перенесён из репозитория protocan-transport, который подключался сабмодулем в CAN_to_RS485. Кадрирование AA 55 с CRC16 поверх любого байтового потока (RS485, RS232, USB CDC), разбор 29-битного идентификатора, общее адресное пространство регистров и каталог с подпиской на поток значений для SETGUI. Состояние живёт в структурах вызывающего, поэтому в одной прошивке поднимается сколько угодно независимых каналов. Порт STM32F4 (USART + DMA) в комплекте. Хостовые тесты test_transport и test_gui проходят.
This commit is contained in:
158
c/protocan-transport/docs/GUI_CATALOG.md
Normal file
158
c/protocan-transport/docs/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` — прибор не тратит на это такты и не
|
||||
теряет точность на промежуточном округлении.
|
||||
Reference in New Issue
Block a user