/** * 自然写教室智能网关管理软件 V1.0 * * protocol_converter.c - BLE到MQTT协议转换模块 * * 功能说明: * - BLE原始帧解析为结构化笔迹数据 * - 笔迹数据编码为MQTT JSON/二进制负载 * - 多种消息类型转换(笔迹/状态/控制) * - 数据压缩与批量打包 * - 消息序列号管理与去重 */ #include #include #include #include #include #include #include /* ======================== 常量与类型定义 ======================== */ /* 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"); }