Сохрани адресацию отдельных регистров GAS 0x3 и выдели слотам тип 0x4 с маской Word0. Добавь единое C-ядро GAS_WATCH2, тонкие Python/JNI-порты и проверки общих эталонных байтов. Совместимость: тип 0x4 больше не обслуживает Modbus Coil. Потребителям PCAN_MSG_MODBUS_COIL и ProtoCanMsgToModbusCoil нужно перейти на GAS_SLOT и отдельный API слотов; регистровый GAS остаётся на 0x3.
203 lines
17 KiB
Markdown
203 lines
17 KiB
Markdown
# 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` сохраняет собственный формат истории; его
|
||
динамическая перенастройка здесь не включается.
|