Files
system-design/software-copyright/04-writech-gateway/protocol/protocol_converter.c
T
2026-03-22 15:24:40 +08:00

636 lines
21 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.
/**
* 自然写教室智能网关管理软件 V1.0
*
* protocol_converter.c - BLE到MQTT协议转换模块
*
* 功能说明:
* - BLE原始帧解析为结构化笔迹数据
* - 笔迹数据编码为MQTT JSON/二进制负载
* - 多种消息类型转换(笔迹/状态/控制)
* - 数据压缩与批量打包
* - 消息序列号管理与去重
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <math.h>
/* ======================== 常量与类型定义 ======================== */
/* BLE帧类型标识 */
#define BLE_FRAME_STROKE 0x01 /* 笔迹坐标帧 */
#define BLE_FRAME_PAGE_TURN 0x02 /* 翻页事件帧 */
#define BLE_FRAME_PEN_STATE 0x03 /* 笔状态帧(抬笔/落笔) */
#define BLE_FRAME_BATTERY 0x04 /* 电量上报帧 */
#define BLE_FRAME_HEARTBEAT 0x05 /* 心跳帧 */
#define BLE_FRAME_OTA_ACK 0x06 /* OTA响应帧 */
/* MQTT消息类型 */
#define MQTT_MSG_STROKE 0x10 /* 笔迹数据消息 */
#define MQTT_MSG_EVENT 0x20 /* 事件通知消息 */
#define MQTT_MSG_STATUS 0x30 /* 设备状态消息 */
#define MQTT_MSG_COMMAND_ACK 0x40 /* 命令应答消息 */
/* 协议参数 */
#define MAX_BATCH_POINTS 64 /* 单批次最大坐标点数 */
#define MAX_JSON_BUFFER 4096 /* JSON缓冲区大小 */
#define MAX_BINARY_PAYLOAD 2048 /* 二进制负载最大长度 */
#define COMPRESS_THRESHOLD 128 /* 触发压缩的数据量阈值(字节) */
#define SEQUENCE_NUM_MAX 65535 /* 序列号最大值 */
/* CRC-16 CCITT多项式 */
#define CRC16_CCITT_POLY 0x1021
/* BLE原始帧头结构 (与笔固件协议一致) */
typedef struct {
uint8_t sync_byte; /* 同步字节 0xAA */
uint8_t frame_type; /* 帧类型 */
uint8_t pen_id[6]; /* 笔MAC地址 */
uint16_t payload_len; /* 负载长度 */
uint16_t sequence; /* 帧序列号 */
} __attribute__((packed)) ble_frame_header_t;
/* 7字节紧凑坐标编码结构 (与笔端一致) */
typedef struct {
uint32_t x_coord : 20; /* X坐标 0-1048575 */
uint32_t y_coord : 20; /* Y坐标 0-1048575 */
uint16_t pressure : 12; /* 压力值 0-4095 */
uint8_t flags : 4; /* 标志位 */
} stroke_point_compact_t;
/* 解码后的笔迹坐标点 */
typedef struct {
float x; /* X坐标(毫米) */
float y; /* Y坐标(毫米) */
float pressure; /* 压力值(归一化 0.0-1.0 */
uint32_t timestamp_ms; /* 时间戳(毫秒) */
uint8_t pen_down; /* 落笔标志 */
} decoded_point_t;
/* MQTT负载结构 */
typedef struct {
char topic[128]; /* MQTT主题 */
uint8_t payload[MAX_BINARY_PAYLOAD]; /* 负载数据 */
uint32_t payload_len; /* 负载长度 */
uint8_t qos; /* QoS等级 */
bool retain; /* 保留标志 */
uint16_t msg_seq; /* 消息序列号 */
} mqtt_message_t;
/* 协议转换器上下文 */
typedef struct {
char gateway_id[32]; /* 网关标识 */
uint16_t next_sequence; /* 下一个消息序列号 */
uint16_t last_ble_seq[64]; /* 各笔最后BLE序列号(去重) */
uint32_t total_converted; /* 总转换消息数 */
uint32_t total_dropped; /* 丢弃的重复消息数 */
uint32_t error_count; /* 错误计数 */
bool use_binary_format; /* 是否使用二进制格式 */
bool compression_enabled; /* 是否启用压缩 */
} protocol_converter_ctx_t;
/* 全局协议转换器实例 */
static protocol_converter_ctx_t g_converter;
/* ======================== CRC校验 ======================== */
/**
* 计算CRC-16 CCITT校验值
* 用于验证BLE帧数据完整性
*/
static uint16_t crc16_ccitt(const uint8_t *data, uint32_t length)
{
uint16_t crc = 0xFFFF;
for (uint32_t i = 0; i < length; i++) {
crc ^= (uint16_t)data[i] << 8;
for (int j = 0; j < 8; j++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ CRC16_CCITT_POLY;
} else {
crc <<= 1;
}
}
}
return crc;
}
/* ======================== BLE帧解析 ======================== */
/**
* 验证BLE帧头有效性
* 检查同步字节、帧类型范围、负载长度合理性
*/
static bool validate_ble_frame(const uint8_t *raw_data, uint32_t raw_len)
{
if (raw_len < sizeof(ble_frame_header_t) + 2) {
/* 数据长度不足(帧头 + CRC-16) */
return false;
}
const ble_frame_header_t *header = (const ble_frame_header_t *)raw_data;
/* 检查同步字节 */
if (header->sync_byte != 0xAA) {
return false;
}
/* 检查帧类型范围 */
if (header->frame_type < BLE_FRAME_STROKE ||
header->frame_type > BLE_FRAME_OTA_ACK) {
return false;
}
/* 检查负载长度合理性 */
uint32_t expected_len = sizeof(ble_frame_header_t) + header->payload_len + 2;
if (expected_len > raw_len || header->payload_len > MAX_BINARY_PAYLOAD) {
return false;
}
/* CRC校验 - 计算帧头+负载的CRC并与尾部CRC比较 */
uint32_t data_len = sizeof(ble_frame_header_t) + header->payload_len;
uint16_t calc_crc = crc16_ccitt(raw_data, data_len);
uint16_t recv_crc = *(uint16_t *)(raw_data + data_len);
if (calc_crc != recv_crc) {
g_converter.error_count++;
return false;
}
return true;
}
/**
* 解码7字节紧凑坐标为浮点坐标
* 坐标单位从点阵码单位转换为毫米
* 压力值归一化到0.0-1.0范围
*/
static void decode_compact_point(const uint8_t *compact_data,
decoded_point_t *point)
{
/* 从7字节紧凑编码中提取各字段 */
uint32_t raw_x = ((uint32_t)compact_data[0] << 12) |
((uint32_t)compact_data[1] << 4) |
((compact_data[2] >> 4) & 0x0F);
uint32_t raw_y = ((uint32_t)(compact_data[2] & 0x0F) << 16) |
((uint32_t)compact_data[3] << 8) |
compact_data[4];
uint16_t raw_pressure = ((uint16_t)compact_data[5] << 4) |
((compact_data[6] >> 4) & 0x0F);
uint8_t flags = compact_data[6] & 0x0F;
/* 坐标转换:点阵码坐标 → 毫米(分辨率约0.3mm/单位) */
point->x = (float)raw_x * 0.3f;
point->y = (float)raw_y * 0.3f;
/* 压力值归一化到 0.0-1.0 */
point->pressure = (float)raw_pressure / 4095.0f;
/* 落笔标志在flags低位 */
point->pen_down = (flags & 0x01) ? 1 : 0;
}
/**
* 解析BLE笔迹帧为坐标点数组
* 返回实际解码的坐标点数量
*/
static int parse_stroke_frame(const uint8_t *payload, uint16_t payload_len,
decoded_point_t *points, int max_points)
{
/* 每个坐标点占7字节紧凑编码 + 4字节时间戳 = 11字节 */
int point_size = 11;
int num_points = payload_len / point_size;
if (num_points > max_points) {
num_points = max_points;
}
for (int i = 0; i < num_points; i++) {
const uint8_t *point_data = payload + (i * point_size);
/* 解码紧凑坐标 */
decode_compact_point(point_data, &points[i]);
/* 提取时间戳 (小端序,4字节毫秒时间戳) */
points[i].timestamp_ms = (uint32_t)point_data[7] |
((uint32_t)point_data[8] << 8) |
((uint32_t)point_data[9] << 16) |
((uint32_t)point_data[10] << 24);
}
return num_points;
}
/* ======================== 序列号去重 ======================== */
/**
* 检查BLE帧序列号是否重复
* 使用滑动窗口检测重复帧,防止BLE重传导致数据重复
*/
static bool is_duplicate_frame(uint8_t pen_index, uint16_t ble_sequence)
{
if (pen_index >= 64) {
return false;
}
uint16_t last_seq = g_converter.last_ble_seq[pen_index];
/* 考虑序列号回绕:如果新序列号在旧序列号的合理范围内则认为重复 */
if (ble_sequence == last_seq) {
g_converter.total_dropped++;
return true;
}
/* 更新最后序列号 */
g_converter.last_ble_seq[pen_index] = ble_sequence;
return false;
}
/**
* 分配下一个MQTT消息序列号
* 单调递增,到达最大值后回绕
*/
static uint16_t allocate_msg_sequence(void)
{
uint16_t seq = g_converter.next_sequence;
g_converter.next_sequence = (seq + 1) % (SEQUENCE_NUM_MAX + 1);
return seq;
}
/* ======================== JSON编码 ======================== */
/**
* 将笔迹坐标数组编码为JSON格式
* 格式: {"pen_id":"xx:xx:xx","seq":N,"points":[{"x":1.2,"y":3.4,"p":0.5,"t":123},...]}
*/
static int encode_stroke_json(const char *pen_id_str,
const decoded_point_t *points, int num_points,
char *json_buf, int buf_size)
{
int offset = 0;
/* JSON头部 */
offset += snprintf(json_buf + offset, buf_size - offset,
"{\"gw\":\"%s\",\"pen\":\"%s\",\"seq\":%u,\"ts\":%lu,\"pts\":[",
g_converter.gateway_id, pen_id_str,
allocate_msg_sequence(), (unsigned long)time(NULL));
/* 编码每个坐标点 */
for (int i = 0; i < num_points && offset < buf_size - 64; i++) {
if (i > 0) {
json_buf[offset++] = ',';
}
offset += snprintf(json_buf + offset, buf_size - offset,
"{\"x\":%.2f,\"y\":%.2f,\"p\":%.3f,\"t\":%u,\"d\":%d}",
points[i].x, points[i].y, points[i].pressure,
points[i].timestamp_ms, points[i].pen_down);
}
/* JSON尾部 */
offset += snprintf(json_buf + offset, buf_size - offset, "]}");
return offset;
}
/**
* 将设备状态编码为JSON格式
* 格式: {"gateway_id":"xx","pen_id":"xx","event":"battery","value":85}
*/
static int encode_status_json(const char *pen_id_str,
const char *event_type,
int value, char *json_buf, int buf_size)
{
return snprintf(json_buf, buf_size,
"{\"gw\":\"%s\",\"pen\":\"%s\",\"event\":\"%s\","
"\"value\":%d,\"ts\":%lu}",
g_converter.gateway_id, pen_id_str, event_type,
value, (unsigned long)time(NULL));
}
/* ======================== 简单LZ压缩 ======================== */
/**
* 简易RLE压缩 - 对二进制负载进行行程编码压缩
* 当连续相同字节超过3个时进行压缩
* 返回压缩后长度,若压缩无效则返回原始长度
*/
static uint32_t rle_compress(const uint8_t *input, uint32_t input_len,
uint8_t *output, uint32_t output_max)
{
if (input_len < COMPRESS_THRESHOLD) {
/* 数据量太小,不压缩 */
memcpy(output, input, input_len);
return input_len;
}
uint32_t out_pos = 0;
uint32_t i = 0;
/* 写入压缩标记头 */
output[out_pos++] = 0x52; /* 'R' - RLE标记 */
output[out_pos++] = 0x4C; /* 'L' */
output[out_pos++] = (input_len >> 8) & 0xFF; /* 原始长度高字节 */
output[out_pos++] = input_len & 0xFF; /* 原始长度低字节 */
while (i < input_len && out_pos < output_max - 3) {
uint8_t current = input[i];
uint32_t run_len = 1;
/* 统计连续相同字节 */
while (i + run_len < input_len &&
input[i + run_len] == current &&
run_len < 255) {
run_len++;
}
if (run_len >= 4) {
/* RLE编码: 转义字节 + 重复次数 + 值 */
output[out_pos++] = 0xFF; /* 转义标记 */
output[out_pos++] = (uint8_t)run_len;
output[out_pos++] = current;
} else {
/* 直接拷贝非重复数据 */
for (uint32_t j = 0; j < run_len && out_pos < output_max; j++) {
if (current == 0xFF) {
/* 原始数据恰好是0xFF,需要转义 */
output[out_pos++] = 0xFF;
output[out_pos++] = 0x01;
output[out_pos++] = 0xFF;
} else {
output[out_pos++] = current;
}
}
}
i += run_len;
}
/* 如果压缩后更大,返回原始数据 */
if (out_pos >= input_len) {
memcpy(output, input, input_len);
return input_len;
}
return out_pos;
}
/* ======================== 核心转换接口 ======================== */
/**
* 初始化协议转换器
* 设置网关标识,清空序列号追踪
*/
int protocol_converter_init(const char *gateway_id, bool use_binary,
bool enable_compression)
{
memset(&g_converter, 0, sizeof(g_converter));
strncpy(g_converter.gateway_id, gateway_id,
sizeof(g_converter.gateway_id) - 1);
g_converter.use_binary_format = use_binary;
g_converter.compression_enabled = enable_compression;
g_converter.next_sequence = 1;
/* 初始化序列号追踪数组 */
memset(g_converter.last_ble_seq, 0xFF, sizeof(g_converter.last_ble_seq));
printf("[协议转换] 初始化完成, 网关=%s, 二进制=%d, 压缩=%d\n",
gateway_id, use_binary, enable_compression);
return 0;
}
/**
* 将MAC地址字节数组转换为字符串表示
*/
static void mac_to_string(const uint8_t mac[6], char *str, int str_len)
{
snprintf(str, str_len, "%02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
/**
* 核心协议转换函数
* 将BLE原始帧转换为MQTT消息
*
* @param raw_ble_data BLE接收到的原始字节流
* @param raw_len 原始数据长度
* @param pen_index 笔在连接表中的索引(0-63)
* @param mqtt_msg 输出: 转换后的MQTT消息
* @return 0=成功, -1=帧无效, -2=重复帧, -3=转换失败
*/
int convert_ble_to_mqtt(const uint8_t *raw_ble_data, uint32_t raw_len,
uint8_t pen_index, mqtt_message_t *mqtt_msg)
{
/* 步骤1: 验证BLE帧 */
if (!validate_ble_frame(raw_ble_data, raw_len)) {
g_converter.error_count++;
return -1;
}
const ble_frame_header_t *header = (const ble_frame_header_t *)raw_ble_data;
const uint8_t *payload = raw_ble_data + sizeof(ble_frame_header_t);
/* 步骤2: 序列号去重 */
if (is_duplicate_frame(pen_index, header->sequence)) {
return -2;
}
/* 获取笔MAC地址字符串 */
char pen_id_str[20];
mac_to_string(header->pen_id, pen_id_str, sizeof(pen_id_str));
/* 步骤3: 根据帧类型进行协议转换 */
char json_buf[MAX_JSON_BUFFER];
int json_len = 0;
switch (header->frame_type) {
case BLE_FRAME_STROKE: {
/* 笔迹坐标帧 → MQTT笔迹数据消息 */
decoded_point_t points[MAX_BATCH_POINTS];
int num_points = parse_stroke_frame(payload, header->payload_len,
points, MAX_BATCH_POINTS);
if (num_points <= 0) {
return -3;
}
/* 构建MQTT Topic: pen/{gateway_id}/stroke */
snprintf(mqtt_msg->topic, sizeof(mqtt_msg->topic),
"pen/%s/stroke", g_converter.gateway_id);
/* 编码为JSON负载 */
json_len = encode_stroke_json(pen_id_str, points, num_points,
json_buf, sizeof(json_buf));
/* 笔迹数据使用QoS 1确保送达 */
mqtt_msg->qos = 1;
mqtt_msg->retain = false;
break;
}
case BLE_FRAME_PAGE_TURN: {
/* 翻页事件 → MQTT事件消息 */
uint16_t page_id = payload[0] | ((uint16_t)payload[1] << 8);
snprintf(mqtt_msg->topic, sizeof(mqtt_msg->topic),
"pen/%s/event", g_converter.gateway_id);
json_len = snprintf(json_buf, sizeof(json_buf),
"{\"gw\":\"%s\",\"pen\":\"%s\",\"event\":\"page_turn\","
"\"page_id\":%u,\"ts\":%lu}",
g_converter.gateway_id, pen_id_str, page_id,
(unsigned long)time(NULL));
mqtt_msg->qos = 1;
mqtt_msg->retain = false;
break;
}
case BLE_FRAME_PEN_STATE: {
/* 笔状态帧 → MQTT事件消息 */
const char *state = (payload[0] == 0x01) ? "pen_down" : "pen_up";
snprintf(mqtt_msg->topic, sizeof(mqtt_msg->topic),
"pen/%s/event", g_converter.gateway_id);
json_len = encode_status_json(pen_id_str, state,
payload[0], json_buf, sizeof(json_buf));
mqtt_msg->qos = 0;
mqtt_msg->retain = false;
break;
}
case BLE_FRAME_BATTERY: {
/* 电量上报帧 → MQTT状态消息 */
uint8_t battery_pct = payload[0];
snprintf(mqtt_msg->topic, sizeof(mqtt_msg->topic),
"gateway/%s/status", g_converter.gateway_id);
json_len = encode_status_json(pen_id_str, "battery",
battery_pct, json_buf, sizeof(json_buf));
/* 电量信息使用QoS 0,允许丢失 */
mqtt_msg->qos = 0;
mqtt_msg->retain = true; /* 保留最新电量 */
break;
}
case BLE_FRAME_HEARTBEAT: {
/* 心跳帧 → 更新设备在线状态,不转发至MQTT */
/* 心跳由设备管理器处理,此处仅记录 */
return 0;
}
default:
return -3;
}
/* 步骤4: 将JSON数据填入MQTT消息负载 */
if (json_len > 0 && json_len < (int)sizeof(mqtt_msg->payload)) {
if (g_converter.compression_enabled &&
json_len > COMPRESS_THRESHOLD) {
/* 压缩JSON负载 */
mqtt_msg->payload_len = rle_compress(
(const uint8_t *)json_buf, json_len,
mqtt_msg->payload, sizeof(mqtt_msg->payload));
} else {
memcpy(mqtt_msg->payload, json_buf, json_len);
mqtt_msg->payload_len = json_len;
}
}
mqtt_msg->msg_seq = allocate_msg_sequence();
g_converter.total_converted++;
return 0;
}
/**
* 将云端MQTT命令消息转换为BLE控制帧
* 支持命令类型:OTA触发、配置更新、校准指令
*
* @param mqtt_payload MQTT消息负载(JSON)
* @param payload_len 负载长度
* @param ble_cmd_buf 输出: BLE命令帧缓冲
* @param buf_size 缓冲区大小
* @return 生成的BLE命令帧长度, -1=失败
*/
int convert_mqtt_to_ble_command(const uint8_t *mqtt_payload,
uint32_t payload_len,
uint8_t *ble_cmd_buf, uint32_t buf_size)
{
/* 简易JSON解析 - 查找command字段 */
const char *json_str = (const char *)mqtt_payload;
const char *cmd_start = strstr(json_str, "\"command\":\"");
if (cmd_start == NULL) {
return -1;
}
cmd_start += strlen("\"command\":\"");
/* 构建BLE命令帧头 */
ble_frame_header_t *cmd_header = (ble_frame_header_t *)ble_cmd_buf;
cmd_header->sync_byte = 0xAA;
cmd_header->sequence = allocate_msg_sequence();
uint8_t *cmd_payload = ble_cmd_buf + sizeof(ble_frame_header_t);
uint16_t cmd_payload_len = 0;
if (strncmp(cmd_start, "ota_start", 9) == 0) {
/* OTA升级启动命令 */
cmd_header->frame_type = BLE_FRAME_OTA_ACK;
cmd_payload[0] = 0x01; /* OTA开始标记 */
cmd_payload_len = 1;
} else if (strncmp(cmd_start, "calibrate", 9) == 0) {
/* 校准命令 */
cmd_header->frame_type = BLE_FRAME_PEN_STATE;
cmd_payload[0] = 0x10; /* 校准指令码 */
cmd_payload_len = 1;
} else {
return -1;
}
cmd_header->payload_len = cmd_payload_len;
/* 追加CRC校验 */
uint32_t frame_len = sizeof(ble_frame_header_t) + cmd_payload_len;
uint16_t crc = crc16_ccitt(ble_cmd_buf, frame_len);
memcpy(ble_cmd_buf + frame_len, &crc, 2);
return frame_len + 2;
}
/**
* 获取协议转换器统计信息
*/
void protocol_converter_get_stats(uint32_t *converted,
uint32_t *dropped,
uint32_t *errors)
{
if (converted) *converted = g_converter.total_converted;
if (dropped) *dropped = g_converter.total_dropped;
if (errors) *errors = g_converter.error_count;
}
/**
* 重置协议转换器统计计数
*/
void protocol_converter_reset_stats(void)
{
g_converter.total_converted = 0;
g_converter.total_dropped = 0;
g_converter.error_count = 0;
printf("[协议转换] 统计计数已重置\n");
}