Files
templates/c/set-protocol/docs/GAS_WATCH2.md
T
Andrey 876b58517c feat(set-protocol): Раздели GAS и добавь пакетный сбор регистров
Сохрани адресацию отдельных регистров GAS 0x3 и выдели слотам тип 0x4 с маской Word0. Добавь единое C-ядро GAS_WATCH2, тонкие Python/JNI-порты и проверки общих эталонных байтов.

Совместимость: тип 0x4 больше не обслуживает Modbus Coil. Потребителям PCAN_MSG_MODBUS_COIL и ProtoCanMsgToModbusCoil нужно перейти на GAS_SLOT и отдельный API слотов; регистровый GAS остаётся на 0x3.
2026-10-23 14:14:48 +03:00

203 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GAS_WATCH2: список адресов и пакетная выборка
> GAS `0x3` использует адреса отдельных 16-битных регистров. GAS SLOT `0x4` — отдельный сервис с маской в Word0 и тремя значениями Word1…Word3. Спецификация: `doc/setcan/protocan/OAP.md`; адреса этого документа остаются регистровыми.
`include/set_gas_watch.h` и `src/set_gas_watch.c` реализуют выбранный клиентом
список GAS-регистров, транзакционную настройку и захват значений в отдельную
структуру. График и архив могут использовать одну готовую выборку.
Ядро — C99, без HAL, часов ОС, heap, глобального состояния и драйверов связи.
Старые `gui_watch_t`, `GAS_WATCH_SET` / `GAS_WATCH_DATA` и ABI
`set_trend_watch_*` сохраняют сигнатуры и побайтовый формат.
Расширение включается явно и не меняет существующую типизированную
телеметрию `set_telemetry` / SETProtocol v2 `SUBSCRIBE`.
## Что подключается к МК
Код `templates` компилируется в прошивку. Она создаёт `set_gas_watch_t`
в собственной RAM, предоставляет `pcan_gas_map_t` и вызывает API из своего
диспетчера команд и таймера. Нового процесса или среды исполнения на МК нет.
| Компонент | Ответственность |
|---|---|
| `set_gas_watch_t` | Собственная копия списка адресов, период, поколение и расписание |
| `set_gas_watch_sample_t` | Значения, достоверность каждого слова, время и номер захвата |
| `pcan_gas_map_t` | Доступ к RAM/обработчикам источников; GAS-адрес не является указателем MCU |
| Порт платы | Блокировка, локальное время, таймер, диспетчер и очередь передачи |
| Python/JNI/Kotlin | Преобразование моделей и pending/active без второго кодека |
| GUI | Имена, типы, масштаб, цвет, видимость и объединение запросов вкладок |
Минимальные исходники: `set_gas_watch.c`, `pcan_gas.c`, `pcan_id.c`,
`set_protocol.c`; заголовки берутся из `include`. Полная CMake/NDK/host-сборка
уже включает новый модуль. Состояние и выборка используют ограниченные
массивы; реальные размеры структур определяются `sizeof` на целевом MCU.
Пример последовательности вызовов в прошивке:
```c
#include "set_gas_watch.h" /* Подключает список GAS-слов и независимый от транспорта захват. */
static set_gas_watch_t watch; /* Хранит одну управляемую подписку в RAM платы; память выделяет проект. */
static set_gas_watch_sample_t sample; /* Сохраняет выборку до завершения работы всех её потребителей. */
/* При старте сессии: set_gas_watch_init(&watch); память и timer предоставляет прошивка. */
/* В диспетчере, под общей блокировкой: set_gas_watch_apply(&watch, &map, payload, size). */
/* При успехе, под той же блокировкой: set_gas_watch_encode_ack(&watch, tx, sizeof(tx)). */
/* После снятия блокировки: отправить ACK транспортом проекта, при ошибке вернуть NACK/статус. */
/* В таймере, под общей блокировкой: проверить set_gas_watch_due(&watch, now_ms). */
/* Когда пора: set_gas_watch_capture(&watch, &map, now_ms, &sample), затем снять блокировку. */
/* В задаче передачи: set_gas_watch_encode_sample(&sample, tx, sizeof(tx)), затем поставить tx в очередь. */
```
Пример показывает места вызовов; проект предоставляет `map`, `payload`,
буферы, `now_ms`, lock и транспорт. Он обязан проверять результаты API.
Для `[0x0000, 0x0001, 0xFF01]` ядро читает три разрозненных GAS-слова
и формирует один логический пакет, не читая промежуточный диапазон.
Адреса передаются при настройке; DATA содержит значения и метаданные.
## Настройка и поколения
Набор ограничен **64 словами по 16 бит**, а не 64 произвольными параметрами.
Многословный параметр занимает несколько позиций. Дубли адресов отклоняются;
владелец подписки объединяет запросы вкладок до отправки.
`set_gas_watch_request` проверяет модель и строит payload.
`set_gas_watch_apply` проверяет весь пакет и карту: все адреса должны
существовать, разрешать чтение и иметь RAM либо callback чтения. Проверка
не вызывает источники. Отказ сохраняет прежние адреса, период, sequence и
расписание. Частичного принятия нет; коды заданы в `set_gas_watch_result_t`.
`config_id` — ненулевой 32-битный номер, назначаемый управляющим клиентом.
Любое изменение периода или порядка/состава адресов требует нового поколения.
После первой команды принимается модульная разность `new_id - old_id`
в диапазоне `1..0x7FFFFFFF`. Переход `0xFFFFFFFF → 1` допустим, ноль
зарезервирован. Разрыв между последовательными поколениями меньше `2^31`.
Поколения нельзя переиспользовать, пока в обмене могут оставаться старые пакеты.
Точный повтор действующего id со всем прежним содержимым успешен и не
сбрасывает захват: потерянный ACK можно запросить повторно. Тот же id
с другим содержимым, старый id и разность `2^31` дают `SET_GAS_WATCH_CONFLICT`.
Остановка — новая конфигурация `period_ms = 0, count = 0`, карта не требуется.
Пустой список с ненулевым периодом и непустой список с нулевым запрещены.
`set_gas_watch_encode_ack` возвращает полную применённую конфигурацию,
включая адреса. Клиент проверяет её через `set_gas_watch_ack` и заменяет
active только после успеха. Новая команда хранится как pending; DATA
сопоставляются с active. ACK уже заменённого pending отвергается.
Ошибка команды/ACK сохраняет прежний active.
Один экземпляр обслуживает одного управляющего клиента. Если устройство
сохранило состояние после переподключения, клиент продолжает номера поколений.
`reset()` адаптера очищает только его pending/active, не посылает stop
и не сбрасывает устройство. Проект может явно вызвать `set_gas_watch_init`
при завершении управляющей сессии, разрешив новой сессии произвольный первый id.
## Захват и синхронизация
Приложение сериализует все обращения к состоянию, изменения карты,
обновления исходного кэша и захват одним подходящим lock/IRQ-портом.
Транзакционность API означает отсутствие частичной настройки при ошибке;
присваивание C-структуры само по себе не даёт межпоточную атомарность.
Ядро не управляет IRQ и не содержит скрытых ожиданий.
Под lock выполняются применение конфигурации, подготовка ACK,
проверка `set_gas_watch_due` и `set_gas_watch_capture`. Сам `capture`
допускает явный захват вне расписания и не проверяет due.
После снятия lock приложение кодирует готовую выборку и передаёт её
транспорту. Кодировщик не обращается к источникам.
При блокировке IRQ callbacks читают только быструю RAM: без запросов
CAN/Modbus, ожидания периферии и выделения памяти. Удалённые источники
обновляют кэш заранее. Общая блокировка согласует чтение кэша, но не
синхронизирует физические измерения нескольких устройств и не определяет
свежесть удалённых данных.
Отказ отдельного источника даёт `valid = 0, value = 0` и не отменяет выборку.
Корректное нулевое значение имеет `valid = 1`. Повреждённая карта целиком
или остановленная подписка возвращают `false` без изменения состояния и выхода.
Первый номер выборки нового поколения — 0; далее он увеличивается по
модулю `2^32`. Каждый захват учитывается, даже если транспорт затем его
потерял. Повторная отправка готовой выборки сохраняет тот же номер.
Время передаётся портом **в миллисекундах устройства**, без подмены часами GUI.
Потребитель разворачивает переполнение времени в длинную ось своего сеанса.
Первый захват разрешён сразу; следующий срок — `now_ms + period_ms`.
После опоздания нет пачки догоняющих выборок. Приложение проверяет таймер
чаще одного раза за `2^31` мс для корректного модульного сравнения u32.
Изменение GAS и конфигурации не меняет готовую выборку. Приложение хранит
её до окончания чтения всеми потребителями, используя очередь или двойной
буфер при необходимости. Входные/выходные структуры и массивы API не
должны перекрываться в памяти.
## Wire payload версии 2
Числа little-endian, размеры ниже в байтах. Выравнивание C-структур
в обмене не используется. Для GUI protocol v1 добавлены отдельные типы:
| Тип GUI v1 | Назначение |
|---|---|
| `0x14 GAS_WATCH2_SET` | Настройка и полное эхо принятой конфигурации |
| `0x15 GAS_WATCH2_DATA` | Выборка; внешний sequence равен 0 |
Внешняя версия остаётся GUI v1. Старые типы `0x12/0x13` не меняются.
Неподдерживаемая команда завершается явным отказом, автоматического fallback нет.
`0x0014 UNSUBSCRIBE` SETProtocol v2 относится к другому кадрированию
и сохраняет смысл. Обёртка нового GAS_WATCH2 для SETProtocol v2 согласуется
отдельно с его существующей типизированной телеметрией.
Конфигурация и ACK: `12 + 2N` байт, максимум 140.
| Смещение | Тип | Значение |
|---:|---|---|
| 0 | u16 | версия payload: 2 |
| 2 | u16 | период, мс |
| 4 | u32 | config_id, не ноль |
| 8 | u16 | N, число адресов: 0..64 |
| 10 | u16 | резерв, строго 0 |
| 12 | N × u16 | уникальные GAS-адреса в порядке значений |
Выборка: `16 + ceil(N/8) + 2N` байт, максимум 152.
| Смещение | Тип | Значение |
|---:|---|---|
| 0 | u16 | версия payload: 2 |
| 2 | u16 | N, число значений: 1..64 |
| 4 | u32 | config_id |
| 8 | u32 | sequence захвата |
| 12 | u32 | timestamp_ms устройства |
| 16 | ceil(N/8) байт | битовая маска достоверности |
| 16 + ceil(N/8) | N × u16 | сырые значения в порядке списка |
Канал i соответствует биту `i % 8` байта `i / 8`, младший бит первым.
Неиспользуемые старшие биты последнего байта нулевые. Недостоверные слова
обязательно нулевые: кодировщик нормализует их, декодировщик отвергает
неканонический вход. Ошибки версии, точной длины, id, ожидаемого количества
или вместимости выхода возвращают `-1` без изменения выходных аргументов.
Успешный результат — число слов, включая отмеченные недостоверными.
Classic CAN требует сегментации логического пакета портом: восемь байт
данных кадра не вмещают всю выборку. Окно Modbus для снимка подключается
отдельным проектным адаптером.
## Клиенты и проверки
`python/protocan/gas_watch.py` предоставляет `GasWatchConfig`,
`GasWatchSample`, `NativeGasWatch`; Android-порт — `GuiGasWatch2.kt` и JNI.
Оба используют C request/ack/decode; недостоверный канал представлен
Python `None` / Kotlin `null`. Старый DLL сообщает о необходимости
пересборки нового режима вместо второго кодека на языке GUI.
Стабильные ABI-символы `set_gas_watch_request`, `set_gas_watch_ack`,
`set_gas_watch_decode` добавлены без изменения версии и прежних символов.
Сборки CMake, прямой host builder и Android NDK включают новый исходник.
Общий эталон — `tests/fixtures/gas-watch-v2.json`.
C-тест `gas_watch_v2` проверяет wire, транзакционность, поколения,
ошибки источников, независимость выборки, bitmap и переполнения.
Python/JVM используют те же байты через настоящие DLL/JNI.
Это изменение общего ядра и адаптеров в `templates`. Переключение внешних
SETGUI/Android на новую ревизию сабмодуля, объединение подписок вкладок
и подключение к диспетчеру конкретной платы выполняются в этих проектах.
Архивный `c/gas-logger` сохраняет собственный формат истории; его
динамическая перенастройка здесь не включается.