/** * @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 #include #include #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 */