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