Files
templates/c/protocan-transport/include/gui_catalog.h
Andrey Kruchinkin 3dc636e012 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 проходят.
2026-08-23 01:15:35 +03:00

156 lines
6.3 KiB
C
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.
/**
* @file gui_catalog.h
* @brief Каталог общего адресного пространства и поток выбранных значений.
*
* Прибор объявляет GUI, какие регистры у него есть и как они называются;
* оператор отмечает нужное, и прибор шлёт только отмеченное пакетами.
* Схема повторяет реестр регистров ST Motor Control Workbench.
*
* Двоичный контракт: docs/GUI_CATALOG.md.
* Зеркало на Python: gui_desktop/core/gas_catalog.py.
*/
#ifndef GUI_CATALOG_H
#define GUI_CATALOG_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "gui_frame.h"
#include "pcan_gas.h"
#ifdef __cplusplus
extern "C" {
#endif
/** Длина записи каталога на линии. */
#define GUI_ENTRY_SIZE 32U
/**
* Длина поля имени. 24 байта - это 12 кириллических символов в UTF-8;
* на 16 байтах не помещалось даже "Температура".
*/
#define GUI_NAME_SIZE 24U
/** Заголовок ответа GAS_CATALOG: total, start_index, count. */
#define GUI_CATALOG_HEADER 6U
/** Сколько записей входит в один кадр. */
#define GUI_ENTRIES_PER_FRAME ((GUI_MAX_PAYLOAD - GUI_CATALOG_HEADER) / GUI_ENTRY_SIZE)
/** Максимум адресов в подписке. */
#ifndef GUI_WATCH_MAX
#define GUI_WATCH_MAX 64U
#endif
/** Формат значения. */
#define GUI_OBJ_U16 0U
#define GUI_OBJ_I16 1U
#define GUI_OBJ_U32 2U /* два регистра, младшее слово первым */
#define GUI_OBJ_I32 3U
#define GUI_OBJ_BITS 4U
/** Доступ и признаки. */
#define GUI_OBJ_READABLE 0x01U
#define GUI_OBJ_WRITABLE 0x02U
#define GUI_OBJ_DEFAULT_WATCH 0x04U
/** Коды единиц измерения; входят в контракт и не перенумеровываются. */
#define GUI_UNIT_NONE 0U
#define GUI_UNIT_VOLT 1U
#define GUI_UNIT_AMPERE 2U
#define GUI_UNIT_CELSIUS 3U
#define GUI_UNIT_PERCENT 4U
#define GUI_UNIT_HERTZ 5U
#define GUI_UNIT_MS 6U
#define GUI_UNIT_SECOND 7U
#define GUI_UNIT_KBPS 8U
#define GUI_UNIT_COUNT 9U
#define GUI_UNIT_RPM 10U
#define GUI_UNIT_WATT 11U
/**
* @brief Описание одного значения в общем адресном пространстве.
*
* Таблица объявляется `static const` и живёт во flash: на МК её незачем
* держать в ОЗУ.
*/
typedef struct {
uint16_t address; /**< адрес первого регистра */
uint8_t type; /**< GUI_OBJ_* */
uint8_t flags; /**< GUI_OBJ_READABLE и прочие */
int8_t scale_pow10; /**< физическое = raw * 10^scale */
uint8_t unit; /**< GUI_UNIT_* */
const char *name; /**< UTF-8, не длиннее GUI_NAME_SIZE */
} gui_object_t;
typedef struct {
const gui_object_t *items;
uint16_t count;
} gui_catalog_t;
/**
* @brief Проверяет таблицу на этапе старта.
*
* Ловит имена длиннее поля и адреса, которых нет в карте GAS: ошибку
* в таблице лучше увидеть при инициализации, чем гадать над пустой
* строкой в GUI.
*
* @param map карта GAS для сверки адресов; NULL - не сверять.
*/
bool gui_catalog_validate(const gui_catalog_t *catalog, const pcan_gas_map_t *map);
/**
* @brief Собирает payload ответа GAS_CATALOG.
*
* @param start_index индекс первой записи;
* @param max_count сколько записей отдать; 0 - сколько влезет в кадр;
* @return длина payload либо 0, если start_index за концом каталога.
*/
size_t gui_catalog_encode(const gui_catalog_t *catalog, uint16_t start_index,
uint16_t max_count, uint8_t *out, size_t out_size);
/* --- Подписка на поток ----------------------------------------------------- */
typedef struct {
uint16_t period_ms; /**< 0 - поток остановлен */
uint16_t count;
uint16_t address[GUI_WATCH_MAX];
uint32_t next_ms; /**< когда слать следующий пакет */
uint32_t sent;
uint32_t skipped; /**< тактов пропущено из-за занятой линии */
} gui_watch_t;
void gui_watch_init(gui_watch_t *watch);
/**
* @brief Применяет payload GAS_WATCH_SET.
*
* Адреса, которых нет в карте, в подписку не берутся - GUI увидит это
* по расхождению count в эхо-ответе.
*
* @return число принятых адресов; при неверном payload подписка не меняется
* и возвращается 0xFFFF.
*/
uint16_t gui_watch_apply(gui_watch_t *watch, const pcan_gas_map_t *map,
const uint8_t *payload, uint16_t size);
/** Собирает 4 байта эхо-ответа: период и число принятых адресов. */
size_t gui_watch_encode_ack(const gui_watch_t *watch, uint8_t *out, size_t out_size);
/** Пора ли слать очередной пакет. */
bool gui_watch_due(const gui_watch_t *watch, uint32_t now_ms);
/**
* @brief Собирает payload GAS_WATCH_DATA из текущих значений карты.
* @return длина payload либо 0, если подписка пуста.
*/
size_t gui_watch_encode_data(const gui_watch_t *watch, const pcan_gas_map_t *map,
uint32_t timestamp_ms, uint8_t *out, size_t out_size);
/** Сдвигает момент следующей отправки. */
void gui_watch_advance(gui_watch_t *watch, uint32_t now_ms);
#ifdef __cplusplus
}
#endif
#endif /* GUI_CATALOG_H */