Перенесён из репозитория protocan-transport, который подключался сабмодулем в CAN_to_RS485. Кадрирование AA 55 с CRC16 поверх любого байтового потока (RS485, RS232, USB CDC), разбор 29-битного идентификатора, общее адресное пространство регистров и каталог с подпиской на поток значений для SETGUI. Состояние живёт в структурах вызывающего, поэтому в одной прошивке поднимается сколько угодно независимых каналов. Порт STM32F4 (USART + DMA) в комплекте. Хостовые тесты test_transport и test_gui проходят.
156 lines
6.3 KiB
C
156 lines
6.3 KiB
C
/**
|
||
* @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 */
|