Files
templates/c/protocan-transport/include/gui_frame.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

186 lines
6.6 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_frame.h
* @brief Транспорт GUI-протокола SETGUI на стороне МК.
*
* A5 5A | ver | type | seq_hi seq_lo | size_hi size_lo | payload | CRC32 LE
*
* Заголовок big-endian, CRC32 (IEEE, как zlib) little-endian, payload
* до 512 байт. Байт в байт совпадает с gui_desktop/core/protocol.py.
*
* Это второй протокол библиотеки и он намеренно отдельный от pcan_frame:
* тот повторяет форму CAN-кадра (8 байт данных) и живёт на полевой шине,
* а здесь нужен большой payload для каталога и потока значений. Общего
* у них только платформенный слой pcan_io_t.
*/
#ifndef GUI_FRAME_H
#define GUI_FRAME_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "pcan_config.h"
#include "pcan_link.h"
#ifdef __cplusplus
extern "C" {
#endif
#define GUI_SOF0 0xA5U
#define GUI_SOF1 0x5AU
#define GUI_PROTOCOL_VERSION 0x01U
#define GUI_HEADER_SIZE 8U /* SOF(2) + ver + type + seq(2) + size(2) */
#define GUI_CRC_SIZE 4U
#define GUI_MAX_PAYLOAD 512U
#define GUI_FRAME_MAX (GUI_HEADER_SIZE + GUI_MAX_PAYLOAD + GUI_CRC_SIZE)
/**
* @brief Наибольший payload, который прибор готов принять.
*
* Ограничивает буфер разборщика. 512 нужны только для WRITE_REGISTERS
* на 254 слова; если такие записи не используются, значение можно
* уменьшить и сэкономить ОЗУ.
*/
#ifndef GUI_RX_PAYLOAD_MAX
#define GUI_RX_PAYLOAD_MAX GUI_MAX_PAYLOAD
#endif
/* Типы сообщений, нужные мосту. Полный список - в protocol.py. */
#define GUI_MSG_PING 0x01U
#define GUI_MSG_DEVICE_INFO 0x02U
#define GUI_MSG_DIAGNOSTICS 0x08U
#define GUI_MSG_READ_REGISTERS 0x09U
#define GUI_MSG_WRITE_REGISTERS 0x0AU
#define GUI_MSG_GAS_CATALOG 0x11U
#define GUI_MSG_GAS_WATCH_SET 0x12U
#define GUI_MSG_GAS_WATCH_DATA 0x13U
#define GUI_MSG_NACK 0x80U
#define GUI_MSG_ACK 0x81U
#define GUI_MSG_ERROR 0x82U
/* Коды результата, общие с GUITransport_ObjectResult и ObjectResult. */
#define GUI_RESULT_OK 0U
#define GUI_RESULT_INVALID_ARGUMENT 1U
#define GUI_RESULT_INVALID_LENGTH 2U
#define GUI_RESULT_NOT_FOUND 3U
#define GUI_RESULT_ACCESS_DENIED 4U
#define GUI_RESULT_INTERNAL 7U
/** CRC32 IEEE: poly 0xEDB88320 с рефлексией, init и финальный XOR 0xFFFFFFFF. */
uint32_t gui_crc32(const uint8_t *data, size_t len);
/**
* @brief Кодирует кадр целиком.
* @param out буфер приёмника;
* @param out_size его размер, нужно `GUI_HEADER_SIZE + size + GUI_CRC_SIZE`;
* @return число записанных байт либо 0 при неверных аргументах.
*/
size_t gui_frame_encode(uint8_t type, uint16_t sequence,
const uint8_t *payload, uint16_t size,
uint8_t *out, size_t out_size);
/** Разобранный кадр; payload указывает внутрь буфера разборщика. */
typedef struct {
uint8_t type;
uint16_t sequence;
uint16_t size;
const uint8_t *payload;
} gui_frame_t;
typedef enum {
GUI_ST_SOF0 = 0,
GUI_ST_SOF1,
GUI_ST_HEADER,
GUI_ST_PAYLOAD,
GUI_ST_CRC
} gui_parse_state_t;
typedef struct {
uint32_t frames;
uint32_t crc_errors;
uint32_t version_errors;
uint32_t length_errors;
uint32_t stray_bytes;
} gui_parse_stats_t;
typedef struct {
gui_parse_state_t state;
uint8_t header[6]; /**< ver, type, seq(2), size(2) */
uint16_t idx;
uint16_t size;
uint8_t crc_bytes[GUI_CRC_SIZE];
uint8_t payload[GUI_RX_PAYLOAD_MAX];
gui_parse_stats_t stats;
} gui_parser_t;
void gui_parser_init(gui_parser_t *p);
/** @return true, если кадр собран и проверен; payload лежит в p->payload. */
bool gui_parser_push(gui_parser_t *p, uint8_t byte, gui_frame_t *out);
typedef void (*gui_frame_cb_t)(const gui_frame_t *frame, void *user);
size_t gui_parser_feed(gui_parser_t *p, const uint8_t *data, size_t len,
gui_frame_cb_t cb, void *user);
/* --- Канал ---------------------------------------------------------------- */
/**
* @brief Экземпляр GUI-канала поверх того же pcan_io_t, что и полевой.
*
* Буфер передачи лежит внутри: кадр собирается целиком и отдаётся в порт
* одной записью, иначе половина пакета в линии сбила бы приёмник.
*/
typedef struct {
pcan_io_t io;
gui_parser_t parser;
gui_frame_cb_t on_frame;
void *user;
uint32_t tx_frames;
uint32_t tx_dropped;
uint8_t tx_buf[GUI_FRAME_MAX];
} gui_link_t;
bool gui_link_init(gui_link_t *link, const pcan_io_t *io,
gui_frame_cb_t on_frame, void *user);
/** @return false, если в очереди передачи не хватило места на весь кадр. */
bool gui_link_send(gui_link_t *link, uint8_t type, uint16_t sequence,
const uint8_t *payload, uint16_t size);
/** Короткий отказ: NACK с кодом результата в payload. */
bool gui_link_nack(gui_link_t *link, uint16_t sequence, uint16_t result);
size_t gui_link_feed(gui_link_t *link, const uint8_t *data, size_t len);
/** Помещается ли ещё кадр с payload указанной длины. */
bool gui_link_can_send(const gui_link_t *link, uint16_t size);
/* --- Мелкие помощники для payload ----------------------------------------- */
static inline uint16_t gui_get_u16(const uint8_t *p)
{
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
}
static inline void gui_put_u16(uint8_t *p, uint16_t value)
{
p[0] = (uint8_t)(value & 0xFFU);
p[1] = (uint8_t)(value >> 8);
}
static inline void gui_put_u32(uint8_t *p, uint32_t value)
{
p[0] = (uint8_t)(value & 0xFFU);
p[1] = (uint8_t)((value >> 8) & 0xFFU);
p[2] = (uint8_t)((value >> 16) & 0xFFU);
p[3] = (uint8_t)((value >> 24) & 0xFFU);
}
#ifdef __cplusplus
}
#endif
#endif /* GUI_FRAME_H */