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