feat(vd960DBN): 实现 DLD960Loop 串口通信协议 (0x7F)

新增:
- docs/DLD960Loop_串口通信协议.md — 协议文档 V1.02
- loop_uart_proto.h/c — 协议实现: checksum/组包/解析/帧状态机/命令状态机

修改:
- usart_biz.c: 使用 lup_feed_byte() 帧解析器替代 timeout heuristic; 波特率修正为 115200
- tcp_json_srv.c/h: loop_param_set/query 真实实现(0x63/0x64), 0xC0 传感器推流, 延迟响应机制
- peripheral_main.c: 添加 tcp_json_push_sensor() 调用, 帧解析器超时保护

校验验证: 5个协议例程 XOR+SUM 全部通过
This commit is contained in:
wangfq
2026-07-02 09:26:34 +08:00
parent 4e75312a0f
commit 4fbda96078
7 changed files with 1522 additions and 64 deletions

View File

@@ -0,0 +1,239 @@
/**
******************************************************************************
* @file loop_uart_proto.h
* @author wangfq
* @version V1.0
* @date 2026-07-02
* @brief DLD960Loop 串口通信协议 (7F) — CH32V208 ↔ DLD960Loop MCU
*
* 帧格式: 7F | Addr | LEN | CMD | Data... | XOR | SUM
* UART: USART2, 115200bps, PA2(Tx)/PA3(Rx)
*
* 该协议是通信MCU(CH32V208)与地感MCU(DLD960Loop/AT32F421)之间的接口。
* 网络接口(TCP JSON/5960)通过本模块向地感MCU下发命令并获取数据。
******************************************************************************
*/
#ifndef __LOOP_UART_PROTO_H__
#define __LOOP_UART_PROTO_H__
#include <stdint.h>
/*===========================================================================
* Protocol Constants
*===========================================================================*/
#define LUP_MAGIC 0x7F // 配置/查询协议
#define LUP_MAGIC_OTA 0x9F // OTA
#define LUP_ADDR_DEFAULT 0x00 // 默认地址
#define LUP_MAX_VALUE_LEN 64 // Value 字段最大长度
#define LUP_MAX_PKG_LEN (1 + 3 + LUP_MAX_VALUE_LEN + 2) // 70 bytes 最大帧
/*===========================================================================
* Command Bytes (CMD)
*===========================================================================*/
#define LUP_CMD_GET_VERSION 0x4A // 获取设备版本号
#define LUP_CMD_RESET 0x6D // 设备复位(无回复)
#define LUP_CMD_FACTORY_INIT 0x92 // 出厂初始化
#define LUP_CMD_SENSITIVITY 0x8A // 读写线圈灵敏度列表
#define LUP_CMD_SET_PARAM 0x63 // 设置多路参数
#define LUP_CMD_GET_PARAM 0x64 // 读取多路参数
#define LUP_CMD_SENSOR_REPORT 0xC0 // 设备主动上报传感信息
/*===========================================================================
* Sensor Types (0xC0 SensType)
*===========================================================================*/
#define LUP_SENS_TYPE_MULTI_COIL 0x0C // 多路线圈传感信息(四路)
/*===========================================================================
* R/W byte for sensitivity (0x8A)
*===========================================================================*/
#define LUP_SENS_RW_READ 0x00
#define LUP_SENS_RW_WRITE 0x01
/*===========================================================================
* 线圈数
*===========================================================================*/
#define LUP_COIL_COUNT 4
/*===========================================================================
* Data Structures
*===========================================================================*/
/* 线圈配置 (0x63 Set / 0x64 Get 中使用) */
typedef struct {
uint8_t sensitivity; // 低4位灵敏度(0~9), 高4位频率等级[4]=高频,[5]=低频
uint8_t loop_delay; // 延时时间 0~200, 0.1s/级
uint8_t output_mode; // 低3位: 0=存在,1=进入脉冲,2=离开脉冲,3=方向
// 高5位: SafeMode 分钟数(0=关闭)
uint8_t exist_mode; // 低4位: 0=永久, 非0=有限分钟
uint8_t direction_mode; // 低3位: 0=触发, 1~6=方向模式
// 高4位: Function_Mode
} LUP_CoilParam;
/* 多路参数设置 (0x63) */
typedef struct {
uint8_t auto_mode; // 0=不启用自动调频, 默认0
uint8_t amount; // 线圈数量
LUP_CoilParam params[LUP_COIL_COUNT];
} LUP_ParamSet;
/* 多路参数读取返回 (0x64 Response) */
typedef struct {
uint8_t auto_mode;
uint8_t amount;
LUP_CoilParam params[LUP_COIL_COUNT];
uint32_t freq[LUP_COIL_COUNT][3]; // [ch][0]=freq1, [1]=freq2, [2]=freq3
} LUP_ParamGet;
/* 单路线圈传感数据单元 (0xC0/0x0C 中的每一路线圈) */
typedef struct {
uint8_t freq_level : 2; // 00=高频33nF, 01=中高43nF, 10=中低66nF, 11=低频76nF
uint8_t direction : 1; // 0=触发, 1=方向判别
uint8_t freq_type : 1; // 0=初始频率, 1=实时频率
uint8_t sensitivity : 4; // 灵敏度等级
// 线圈评估条件
uint8_t condition : 4; // 环境状态评估值(0=正常, 值越大干扰越大)
uint8_t loop_state : 1; // 0=正常, 1=断开
uint8_t car_state : 1; // 0=无车, 1=有车
uint8_t misc_type : 2; // 0=时间量, 1=断开次数, 2=车流量
uint32_t freq; // 频率 (3 bytes, little-endian)
uint16_t variation; // 变化量 (2 bytes, little-endian)
union {
uint32_t passtime_ms5; // 通过时间/车间距, 5ms单位 (misc_type=0)
uint32_t cut_amount; // 线圈断开次数 (misc_type=1)
uint32_t flow_amount; // 车流量数 (misc_type=2)
} misc;
} LUP_CoilSensor;
/* 主动上报传感信息 (0xC0/0x0C) */
typedef struct {
uint8_t sens_type; // 0x0C
uint8_t sub_amount; // 分包数量(高4位), 0=无分包
uint8_t sub_sequence; // 当前分包序号(低4位), 1-based
uint8_t coil_count; // 线圈数 (解析出的)
LUP_CoilSensor coils[LUP_COIL_COUNT];
} LUP_SensorReport;
/* 灵敏度读写 (0x8A) */
typedef struct {
uint8_t rw; // 0=Read, 1=Write
uint8_t amount; // 灵敏度值数量
uint16_t sens_in[LUP_COIL_COUNT]; // 触发灵敏度
uint16_t sens_out[LUP_COIL_COUNT]; // 释放灵敏度
} LUP_Sensitivity;
/* 版本信息 (0x4A Response) */
typedef struct {
uint8_t status; // Status byte
uint8_t hard_main;
uint8_t hard_sub;
uint8_t hard_ssub;
uint8_t soft_main;
uint8_t soft_sub;
uint8_t soft_ssub;
char version_str[32]; // 格式化字符串
} LUP_VersionInfo;
/* 命令状态机 */
typedef enum {
LUP_STATE_IDLE = 0, // 空闲
LUP_STATE_WAIT_RESPONSE, // 等待响应
LUP_STATE_RESPONSE_READY, // 响应已就绪
LUP_STATE_TIMEOUT // 超时
} LUP_CmdState;
/* 挂起的命令跟踪 */
typedef struct {
uint8_t pending_cmd; // 当前等待响应的命令字节
uint32_t send_tick; // 发送时刻 (ms)
uint32_t timeout_ms; // 超时时间
LUP_CmdState state;
// 响应数据缓冲区
uint8_t resp_buf[LUP_MAX_PKG_LEN];
uint16_t resp_len;
} LUP_CmdTracker;
/*===========================================================================
* Global State
*===========================================================================*/
extern LUP_CmdTracker g_lup_cmd;
/*===========================================================================
* Public API
*===========================================================================*/
/* --- Checksum --- */
uint8_t lup_calc_xor(const uint8_t *data, uint16_t len);
uint8_t lup_calc_sum(const uint8_t *data, uint16_t len);
int lup_verify_checksum(const uint8_t *pkg, uint16_t len);
void lup_append_checksum(uint8_t *pkg);
/* --- Packet Builders (returns total packet length) --- */
uint16_t lup_build_get_version(uint8_t *buf);
uint16_t lup_build_reset(uint8_t *buf);
uint16_t lup_build_factory_init(uint8_t *buf);
uint16_t lup_build_sensitivity_read(uint8_t *buf);
uint16_t lup_build_sensitivity_write(uint8_t *buf, const LUP_Sensitivity *sens);
uint16_t lup_build_set_param(uint8_t *buf, const LUP_ParamSet *ps);
uint16_t lup_build_get_param(uint8_t *buf);
/* --- Packet Parsers (returns 0=success, <0=error) --- */
int lup_parse_version(const uint8_t *pkg, uint16_t len, LUP_VersionInfo *info);
int lup_parse_factory_init_resp(const uint8_t *pkg, uint16_t len, uint8_t *success);
int lup_parse_sensitivity_resp(const uint8_t *pkg, uint16_t len, LUP_Sensitivity *sens);
int lup_parse_param_get(const uint8_t *pkg, uint16_t len, LUP_ParamGet *pg);
int lup_parse_sensor_report(const uint8_t *pkg, uint16_t len, LUP_SensorReport *report);
int lup_parse_set_param_resp(const uint8_t *pkg, uint16_t len, uint8_t *success);
/* --- Command State Machine --- */
void lup_cmd_begin(uint8_t cmd, uint32_t timeout_ms);
void lup_cmd_done(void);
int lup_cmd_check_timeout(void);
void lup_cmd_on_response(const uint8_t *pkg, uint16_t len);
/* --- High-level Commands (send + wait handled by state machine) --- */
void lup_send_get_version(void);
void lup_send_reset(void);
void lup_send_factory_init(void);
void lup_send_sensitivity_read(void);
void lup_send_set_param(const LUP_ParamSet *ps);
void lup_send_get_param(void);
/* --- Mid-packet ACK for 0xC0 multi-packet --- */
uint16_t lup_build_sensor_ack(uint8_t *buf, uint8_t sens_type,
uint8_t seq, uint8_t sub_amount);
/* --- UART Frame Parser (called from USART2 ISR context) --- */
typedef enum {
LUP_FRAME_STATE_IDLE = 0, // 等待 0x7F
LUP_FRAME_STATE_HEADER, // 接收 Header (Addr, LEN, CMD)
LUP_FRAME_STATE_VALUE, // 接收 Value
LUP_FRAME_STATE_CHECK, // 接收 Checksum
LUP_FRAME_STATE_COMPLETE // 帧完成
} LUP_FrameState;
typedef struct {
LUP_FrameState state;
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t idx; // 当前写入位置
uint16_t value_len; // 期望的 Value 长度 (从 LEN 字段解析)
uint16_t value_idx; // 当前已接收的 Value 字节数
} LUP_FrameParser;
extern LUP_FrameParser g_lup_parser;
/* 喂一个字节给帧解析器,返回 1 表示帧接收完成 */
int lup_feed_byte(uint8_t byte);
/* 取完整帧的指针和数据长度 */
const uint8_t *lup_frame_data(void);
uint16_t lup_frame_len(void);
/* 重置帧解析器 */
void lup_frame_reset(void);
/* --- Process a complete frame (call from main loop context) --- */
void lup_process_frame(const uint8_t *pkg, uint16_t len);
#endif /* __LOOP_UART_PROTO_H__ */

View File

@@ -44,6 +44,17 @@ typedef enum {
#define JSON_CODE_DATA_TOO_LONG 6 #define JSON_CODE_DATA_TOO_LONG 6
#define JSON_CODE_NOT_AUTHED 7 #define JSON_CODE_NOT_AUTHED 7
/*===========================================================================
* Pending Loop MCU Command Response (deferred)
*===========================================================================*/
typedef struct {
uint8_t active; // 1 = pending response
uint8_t socket; // TCP socket to reply to
uint32_t msg_id; // Original msg_id
char cmd[32]; // Original command string
uint32_t deadline; // ms deadline for timeout
} TcpJsonPending;
/*=========================================================================== /*===========================================================================
* Externs * Externs
*===========================================================================*/ *===========================================================================*/
@@ -51,6 +62,7 @@ extern uint8_t g_json_socket_listen; // TCP socket ID (listen + data)
extern TcpJsonAuthState g_json_auth_state; extern TcpJsonAuthState g_json_auth_state;
extern uint32_t g_json_auth_timer; // ms timer for auth timeout extern uint32_t g_json_auth_timer; // ms timer for auth timeout
extern uint8_t g_json_pwd_retry; // password retry counter extern uint8_t g_json_pwd_retry; // password retry counter
extern TcpJsonPending g_json_pending; // Pending Loop MCU command state
/*=========================================================================== /*===========================================================================
* API * API
@@ -58,5 +70,6 @@ extern uint8_t g_json_pwd_retry; // password retry counter
void tcp_json_srv_init(void); // Create listen socket on port 5960 void tcp_json_srv_init(void); // Create listen socket on port 5960
void tcp_json_handle_sock_int(uint8_t socketid, uint8_t intstat); // Socket interrupt handler void tcp_json_handle_sock_int(uint8_t socketid, uint8_t intstat); // Socket interrupt handler
void tcp_json_poll(void); // Periodic poll (auth timeout, etc.) void tcp_json_poll(void); // Periodic poll (auth timeout, etc.)
void tcp_json_push_sensor(void); // Push sensor data (0xC0) to TCP client
#endif /* __TCP_JSON_SRV_H__ */ #endif /* __TCP_JSON_SRV_H__ */

View File

@@ -0,0 +1,679 @@
/**
******************************************************************************
* @file loop_uart_proto.c
* @author wangfq
* @version V1.0
* @date 2026-07-02
* @brief DLD960Loop 串口通信协议实现
* - 校验计算 (XOR + SUM)
* - 命令组包 / 响应解析
* - 帧接收状态机 (替代 timeout heuristic)
* - 命令发送 + 超时跟踪
******************************************************************************
*/
#include "CONFIG.h"
#include "loop_uart_proto.h"
#include "cmcng.h"
#include <string.h>
/*===========================================================================
* Global State
*===========================================================================*/
LUP_CmdTracker g_lup_cmd = {0, 0, 0, LUP_STATE_IDLE, {0}, 0};
/* --- Frame Parser --- */
LUP_FrameParser g_lup_parser = {
.state = LUP_FRAME_STATE_IDLE,
.idx = 0,
.value_len = 0,
.value_idx = 0
};
/*===========================================================================
* Checksum
*===========================================================================*/
/*
* XOR 校验: 从 Addr 字节开始,到命令数据的最后一个字节结束
*/
uint8_t lup_calc_xor(const uint8_t *data, uint16_t len)
{
uint8_t x = 0;
uint16_t i;
for (i = 0; i < len; i++) {
x ^= data[i];
}
return x;
}
/*
* SUM 校验: 从 Addr 字节开始,到命令数据的最后一个字节结束
*/
uint8_t lup_calc_sum(const uint8_t *data, uint16_t len)
{
uint8_t s = 0;
uint16_t i;
for (i = 0; i < len; i++) {
s += data[i];
}
return s;
}
/*
* 校验整包: pkg = [7F] [Addr] [LEN] [CMD] [Data...] [XOR] [SUM]
* Checksum covers: Addr + LEN_field + Value (=CMD + Data) = 2 + LEN bytes
* starting at pkg[1]
*/
int lup_verify_checksum(const uint8_t *pkg, uint16_t len)
{
if (len < 6) return -1; // 最小: 7F + 3 header + 2 checksum = 6
uint8_t pkg_len = pkg[2]; // LEN field
uint16_t check_len = 2 + pkg_len; // Addr(1)+LEN_field(1)+Value(LEN)
if (len < check_len + 3) return -4; // Not enough data: Magic(1) + covered + Check(2)
uint8_t expect_xor = lup_calc_xor(pkg + 1, check_len);
uint8_t expect_sum = lup_calc_sum(pkg + 1, check_len);
uint8_t got_xor = pkg[1 + check_len];
uint8_t got_sum = pkg[2 + check_len];
if (expect_xor != got_xor) return -2;
if (expect_sum != got_sum) return -3;
return 0;
}
/*
* 为已填充好 Data 的 buf 追加 checksum。
* buf 格式: [7F] [Addr] [LEN] [CMD] [Data...]
* Checksum 覆盖: Addr(1) + LEN(1) + Value(LEN) = 2 + LEN bytes
* Output: [7F][Addr][LEN][CMD][Data...][XOR][SUM]
*/
void lup_append_checksum(uint8_t *pkg)
{
// pkg[0]=7F, pkg[1]=Addr, pkg[2]=LEN, pkg[3]=CMD, ...
uint8_t total_len = 1 + 3 + pkg[2] + 2; // Magic(1) + Header(3) + Value(LEN) + Check(2)
uint16_t check_len = 2 + pkg[2]; // Addr(1) + LEN_field(1) + Value(LEN)
pkg[total_len - 2] = lup_calc_xor(pkg + 1, check_len);
pkg[total_len - 1] = lup_calc_sum(pkg + 1, check_len);
}
/*===========================================================================
* Packet Builders
*===========================================================================*/
/*
* 0x4A — 获取设备版本号
* Req: 7F 00 01 4A 4B 4B
*/
uint16_t lup_build_get_version(uint8_t *buf)
{
buf[0] = LUP_MAGIC; // 7F
buf[1] = LUP_ADDR_DEFAULT; // 0x00
buf[2] = 0x01; // LEN = 1 (CMD byte)
buf[3] = LUP_CMD_GET_VERSION; // 0x4A
lup_append_checksum(buf);
return 6; // Magic(1) + Header(3) + Check(2)
}
/*
* 0x6D — 设备复位 (无回复)
* Req: 7F 00 01 6D 6C 6E
*/
uint16_t lup_build_reset(uint8_t *buf)
{
buf[0] = LUP_MAGIC;
buf[1] = LUP_ADDR_DEFAULT;
buf[2] = 0x01;
buf[3] = LUP_CMD_RESET;
lup_append_checksum(buf);
return 6;
}
/*
* 0x92 — 出厂初始化
* Req: 7F 00 01 92 93 93
*/
uint16_t lup_build_factory_init(uint8_t *buf)
{
buf[0] = LUP_MAGIC;
buf[1] = LUP_ADDR_DEFAULT;
buf[2] = 0x01;
buf[3] = LUP_CMD_FACTORY_INIT;
lup_append_checksum(buf);
return 6;
}
/*
* 0x8A — 读灵敏度列表
* Req: 7F 00 04 8A 00 00 ...
*/
uint16_t lup_build_sensitivity_read(uint8_t *buf)
{
buf[0] = LUP_MAGIC;
buf[1] = LUP_ADDR_DEFAULT;
buf[2] = 0x04; // LEN = 4 (CMD + R/W + Amount + reserved?)
buf[3] = LUP_CMD_SENSITIVITY; // 0x8A
// Value: R/W + Amount (2 bytes, all zeros for read)
// 协议说 R/W: 1 byte, Amount: 1 byte, 后面是 Amount * (SensityIn + SensityOut)
// 对于 Read: R/W=0, Amount 忽略
buf[4] = LUP_SENS_RW_READ;
buf[5] = 0x00; // Amount (ignored for read)
buf[6] = 0x00; // padding?
buf[7] = 0x00; // padding?
lup_append_checksum(buf);
return 10; // Magic(1)+Header(3)+Value(4)+Check(2)
}
/*
* 0x8A — 写灵敏度列表
*/
uint16_t lup_build_sensitivity_write(uint8_t *buf, const LUP_Sensitivity *sens)
{
uint8_t i;
uint16_t idx = 0;
buf[idx++] = LUP_MAGIC;
buf[idx++] = LUP_ADDR_DEFAULT;
// LEN field: 2 + 2*sens->amount
uint8_t val_len = 2 + 2 * sens->amount;
buf[idx++] = val_len;
buf[idx++] = LUP_CMD_SENSITIVITY;
buf[idx++] = sens->rw; // 0x01 (Write)
buf[idx++] = sens->amount;
for (i = 0; i < sens->amount; i++) {
buf[idx++] = (uint8_t)(sens->sens_in[i] & 0xFF);
buf[idx++] = (uint8_t)((sens->sens_in[i] >> 8) & 0xFF);
buf[idx++] = (uint8_t)(sens->sens_out[i] & 0xFF);
buf[idx++] = (uint8_t)((sens->sens_out[i] >> 8) & 0xFF);
}
lup_append_checksum(buf);
return idx + 2; // +2 for checksum bytes
}
/*
* 0x63 — 设置多路参数
* Value: AutoMode(1) + Amount(1) + Amount*(Param[5])
*/
uint16_t lup_build_set_param(uint8_t *buf, const LUP_ParamSet *ps)
{
uint8_t i;
uint16_t idx = 0;
buf[idx++] = LUP_MAGIC;
buf[idx++] = LUP_ADDR_DEFAULT;
// LEN = 2 + 5 * Amount
uint8_t val_len = 2 + 5 * ps->amount;
buf[idx++] = val_len;
buf[idx++] = LUP_CMD_SET_PARAM;
buf[idx++] = ps->auto_mode;
buf[idx++] = ps->amount;
for (i = 0; i < ps->amount; i++) {
buf[idx++] = ps->params[i].sensitivity;
buf[idx++] = ps->params[i].loop_delay;
buf[idx++] = ps->params[i].output_mode;
buf[idx++] = ps->params[i].exist_mode;
buf[idx++] = ps->params[i].direction_mode;
}
lup_append_checksum(buf);
return idx + 2;
}
/*
* 0x64 — 读取多路参数
* Req: 7F 00 01 64 ...
*/
uint16_t lup_build_get_param(uint8_t *buf)
{
buf[0] = LUP_MAGIC;
buf[1] = LUP_ADDR_DEFAULT;
buf[2] = 0x01; // LEN = 1
buf[3] = LUP_CMD_GET_PARAM; // 0x64
lup_append_checksum(buf);
return 6;
}
/*
* 0xC0 mid-packet ACK
* Req: 7F | 00 | 07 | C0 | SensType | Seq | SubAmount | XOR | SUM
*/
uint16_t lup_build_sensor_ack(uint8_t *buf, uint8_t sens_type,
uint8_t seq, uint8_t sub_amount)
{
buf[0] = LUP_MAGIC;
buf[1] = LUP_ADDR_DEFAULT;
buf[2] = 0x07; // LEN = 7 (CMD + SensType + Seq + SubAmount)
buf[3] = LUP_CMD_SENSOR_REPORT;
buf[4] = sens_type;
buf[5] = seq;
buf[6] = sub_amount;
buf[7] = 0x00; // padding
buf[8] = 0x00;
buf[9] = 0x00;
buf[10] = 0x00;
lup_append_checksum(buf);
return 13; // Magic(1)+Header(3)+Value(7)+Check(2)
}
/*===========================================================================
* Packet Parsers
*===========================================================================*/
/*
* Parse 0x4A Response:
* Header: 00 08 4A (Addr, LEN=8, CMD)
* Value: Status + Hard_Main + Hard_Sub + Hard_SSub + Soft_Main + Soft_Sub + Soft_SSub
*/
int lup_parse_version(const uint8_t *pkg, uint16_t len, LUP_VersionInfo *info)
{
if (len < 10) return -1; // 1+3+7+2 = 13 min
// pkg layout: [7F][Addr=0][LEN=8][CMD=0x4A][Status...7bytes][XOR][SUM]
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_GET_VERSION) return -2;
if (pkg[2] < 7) return -3; // LEN should be >= 7
const uint8_t *val = pkg + 4; // skip magic + header(3)
info->status = val[0];
info->hard_main = val[1];
info->hard_sub = val[2];
info->hard_ssub = val[3];
info->soft_main = val[4];
info->soft_sub = val[5];
info->soft_ssub = val[6];
return 0;
}
/*
* Parse 0x92 Response:
* Value: 00/01 (00=success)
*/
int lup_parse_factory_init_resp(const uint8_t *pkg, uint16_t len, uint8_t *success)
{
if (len < 7) return -1;
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_FACTORY_INIT) return -2;
*success = (pkg[4] == 0x00) ? 1 : 0;
return 0;
}
/*
* Parse 0x8A Response:
* Value: Amount (1byte) + Amount * SensityValue (2bytes each)
*/
int lup_parse_sensitivity_resp(const uint8_t *pkg, uint16_t len, LUP_Sensitivity *sens)
{
uint8_t i;
if (len < 6) return -1;
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SENSITIVITY) return -2;
const uint8_t *val = pkg + 4;
uint8_t amount = val[0];
if (amount > LUP_COIL_COUNT) amount = LUP_COIL_COUNT;
sens->amount = amount;
sens->rw = 0; // response, not request
for (i = 0; i < amount; i++) {
uint16_t sv = val[1 + i * 2] | ((uint16_t)val[2 + i * 2] << 8);
// Response returns unified SensityValue (not split in/out)
sens->sens_in[i] = sv;
sens->sens_out[i] = sv;
}
return 0;
}
/*
* Parse 0x64 Response:
* Value: AutoMode(1) + Amount(1) + Amount * [Sensitivity, Loop_Delay, Output_Mode,
* Exist_Mode, Direction_Mode, freq1(2), freq2(2), freq3(2)]
* Each coil = 8 bytes
*/
int lup_parse_param_get(const uint8_t *pkg, uint16_t len, LUP_ParamGet *pg)
{
uint8_t i;
if (len < 6) return -1;
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_GET_PARAM) return -2;
const uint8_t *val = pkg + 4;
pg->auto_mode = val[0];
pg->amount = val[1];
if (pg->amount > LUP_COIL_COUNT) pg->amount = LUP_COIL_COUNT;
for (i = 0; i < pg->amount; i++) {
const uint8_t *c = val + 2 + i * 8;
pg->params[i].sensitivity = c[0];
pg->params[i].loop_delay = c[1];
pg->params[i].output_mode = c[2];
pg->params[i].exist_mode = c[3];
pg->params[i].direction_mode = c[4];
pg->freq[i][0] = c[5] | ((uint32_t)c[6] << 8);
pg->freq[i][1] = c[7] | ((uint32_t)c[8] << 8);
pg->freq[i][2] = c[9] | ((uint32_t)c[10] << 8);
}
return 0;
}
/*
* Parse 0x63 Response (Set Param):
* Value: Status(1 byte, 0x00=success)
*/
int lup_parse_set_param_resp(const uint8_t *pkg, uint16_t len, uint8_t *success)
{
if (len < 7) return -1;
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SET_PARAM) return -2;
*success = (pkg[4] == 0x00) ? 1 : 0;
return 0;
}
/*
* Parse 0xC0/0x0C — 多路线圈传感信息
*
* 每路线圈数据单元 = 11 bytes:
* [0] 配置1: freq_level(2) | Direction(1) | freq_type(1) | sens(4)
* [1] 评估条件: condition(4) | loop_state(1) | car_state(1) | misc_type(2)
* [2-4] 频率 (3 bytes, LE)
* [5-6] 变化量 (2 bytes, LE)
* [7-10] 杂项 (4 bytes)
*/
int lup_parse_sensor_report(const uint8_t *pkg, uint16_t len, LUP_SensorReport *report)
{
uint8_t i;
if (len < 9) return -1; // 7F + 3 header + 2 sensType/SubPkgFlag + 2 checksum min
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SENSOR_REPORT) return -2;
uint8_t sens_type = pkg[4];
if (sens_type != LUP_SENS_TYPE_MULTI_COIL) return -3;
uint8_t sub_pkg = pkg[5];
report->sens_type = sens_type;
report->sub_amount = (sub_pkg >> 4) & 0x0F;
report->sub_sequence = sub_pkg & 0x0F;
// 计算线圈数: Value = sensType(1) + SubPkgFlag(1) + coil_count * 11
uint16_t val_len = pkg[2]; // LEN field
uint16_t data_offset = 6; // 跳过 7F + Addr + LEN + CMD + SensType + SubPkgFlag
uint16_t data_len = val_len - 2; // 减去 SensType + SubPkgFlag
uint8_t coil_count = data_len / 11;
if (coil_count > LUP_COIL_COUNT) coil_count = LUP_COIL_COUNT;
report->coil_count = coil_count;
for (i = 0; i < coil_count; i++) {
const uint8_t *c = pkg + data_offset + i * 11;
LUP_CoilSensor *cs = &report->coils[i];
cs->freq_level = (c[0] >> 6) & 0x03;
cs->direction = (c[0] >> 5) & 0x01;
cs->freq_type = (c[0] >> 4) & 0x01;
cs->sensitivity = c[0] & 0x0F;
cs->condition = (c[1] >> 4) & 0x0F;
cs->loop_state = (c[1] >> 3) & 0x01;
cs->car_state = (c[1] >> 2) & 0x01;
cs->misc_type = c[1] & 0x03;
cs->freq = c[2] | ((uint32_t)c[3] << 8) | ((uint32_t)c[4] << 16);
cs->variation = c[5] | ((uint16_t)c[6] << 8);
cs->misc.passtime_ms5 = c[7] | ((uint32_t)c[8] << 8)
| ((uint32_t)c[9] << 16) | ((uint32_t)c[10] << 24);
}
return 0;
}
/*===========================================================================
* Command State Machine
*===========================================================================*/
void lup_cmd_begin(uint8_t cmd, uint32_t timeout_ms)
{
g_lup_cmd.pending_cmd = cmd;
g_lup_cmd.send_tick = mstick();
g_lup_cmd.timeout_ms = timeout_ms;
g_lup_cmd.state = LUP_STATE_WAIT_RESPONSE;
g_lup_cmd.resp_len = 0;
memset(g_lup_cmd.resp_buf, 0, sizeof(g_lup_cmd.resp_buf));
}
void lup_cmd_done(void)
{
g_lup_cmd.pending_cmd = 0;
g_lup_cmd.state = LUP_STATE_IDLE;
g_lup_cmd.resp_len = 0;
}
int lup_cmd_check_timeout(void)
{
if (g_lup_cmd.state != LUP_STATE_WAIT_RESPONSE) return 0;
if (mstick() - g_lup_cmd.send_tick > g_lup_cmd.timeout_ms) {
g_lup_cmd.state = LUP_STATE_TIMEOUT;
PRINT("LUP: cmd 0x%02X timeout\n", g_lup_cmd.pending_cmd);
return 1;
}
return 0;
}
/*
* 收到响应时调用 — 如果当前有挂起命令且 CMD 匹配,则保存响应
*/
void lup_cmd_on_response(const uint8_t *pkg, uint16_t len)
{
if (g_lup_cmd.state != LUP_STATE_WAIT_RESPONSE) return;
if (len < 4) return;
uint8_t resp_cmd = pkg[3];
if (resp_cmd == g_lup_cmd.pending_cmd) {
// 保存响应数据
uint16_t copy_len = (len < sizeof(g_lup_cmd.resp_buf)) ? len : sizeof(g_lup_cmd.resp_buf);
memcpy(g_lup_cmd.resp_buf, pkg, copy_len);
g_lup_cmd.resp_len = copy_len;
g_lup_cmd.state = LUP_STATE_RESPONSE_READY;
}
}
/*===========================================================================
* High-level Send Commands
*===========================================================================*/
void lup_send_get_version(void)
{
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t len = lup_build_get_version(buf);
UART2_SendString(buf, len);
lup_cmd_begin(LUP_CMD_GET_VERSION, 200); // 200ms timeout
}
void lup_send_reset(void)
{
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t len = lup_build_reset(buf);
UART2_SendString(buf, len);
// No response expected
}
void lup_send_factory_init(void)
{
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t len = lup_build_factory_init(buf);
UART2_SendString(buf, len);
lup_cmd_begin(LUP_CMD_FACTORY_INIT, 500);
}
void lup_send_sensitivity_read(void)
{
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t len = lup_build_sensitivity_read(buf);
UART2_SendString(buf, len);
lup_cmd_begin(LUP_CMD_SENSITIVITY, 200);
}
void lup_send_set_param(const LUP_ParamSet *ps)
{
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t len = lup_build_set_param(buf, ps);
UART2_SendString(buf, len);
lup_cmd_begin(LUP_CMD_SET_PARAM, 500);
}
void lup_send_get_param(void)
{
uint8_t buf[LUP_MAX_PKG_LEN];
uint16_t len = lup_build_get_param(buf);
UART2_SendString(buf, len);
lup_cmd_begin(LUP_CMD_GET_PARAM, 500);
}
/*===========================================================================
* Frame Parser (called per-byte from USART2 ISR / timer context)
*
* 帧格式: [7F] [Addr] [LEN] [CMD] [Value: LEN bytes] [XOR] [SUM]
* LEN = Value 字段的长度,包括 CMD byte
* 所以: Value data bytes = LEN - 1 (减掉 CMD)
*
* 状态机:
* IDLE → 找 0x7F → HEADER
* HEADER → 收完 3 bytes Header → VALUE (若 LEN>1)
* VALUE → 收完 data_bytes → CHECK
* CHECK → 收完 2 bytes → COMPLETE
*===========================================================================*/
void lup_frame_reset(void)
{
g_lup_parser.state = LUP_FRAME_STATE_IDLE;
g_lup_parser.idx = 0;
g_lup_parser.value_len = 0;
g_lup_parser.value_idx = 0;
memset(g_lup_parser.buf, 0, sizeof(g_lup_parser.buf));
}
const uint8_t *lup_frame_data(void)
{
return g_lup_parser.buf;
}
uint16_t lup_frame_len(void)
{
return g_lup_parser.idx;
}
/*
* 喂一个字节。返回 1 = 帧接收完成(在 buf 中)
*/
int lup_feed_byte(uint8_t byte)
{
LUP_FrameParser *p = &g_lup_parser;
switch (p->state) {
case LUP_FRAME_STATE_IDLE:
if (byte == LUP_MAGIC) {
p->buf[0] = byte;
p->idx = 1;
p->state = LUP_FRAME_STATE_HEADER;
}
break;
case LUP_FRAME_STATE_HEADER:
p->buf[p->idx++] = byte;
if (p->idx == 4) {
// Header 收完: [7F][Addr][LEN][CMD]
// Value data bytes = LEN - 1 (CMD byte 已计入 LEN)
uint8_t len_field = p->buf[2];
uint16_t data_bytes; // Value 中除 CMD 之外的 data 字节数
if (len_field < 1) {
data_bytes = 0;
} else {
data_bytes = len_field - 1;
}
if (data_bytes > LUP_MAX_VALUE_LEN) {
// 非法长度,丢弃
lup_frame_reset();
break;
}
if (data_bytes > 0) {
p->value_len = data_bytes;
p->value_idx = 0;
p->state = LUP_FRAME_STATE_VALUE;
} else {
// 无 Data直接等 Checksum
p->value_len = 0;
p->value_idx = 0;
p->state = LUP_FRAME_STATE_CHECK;
}
}
break;
case LUP_FRAME_STATE_VALUE:
p->buf[p->idx++] = byte;
p->value_idx++;
if (p->value_idx >= p->value_len) {
p->state = LUP_FRAME_STATE_CHECK;
}
break;
case LUP_FRAME_STATE_CHECK:
p->buf[p->idx++] = byte;
if (p->idx >= p->value_len + 1 + 3 + 2) { // Magic(1) + Header(3) + Value data + Check(2)
p->state = LUP_FRAME_STATE_COMPLETE;
return 1;
}
break;
case LUP_FRAME_STATE_COMPLETE:
// 不应该到达这里,调用方应在 COMPLETE 后 reset
lup_frame_reset();
break;
}
return 0;
}
/*
* 收到完整帧后的处理:
* 1. 校验 checksum
* 2. 如果是当前命令的响应 → 通知状态机
* 3. 如果是主动上报 (0xC0) → 处理传感器数据
*/
void lup_process_frame(const uint8_t *pkg, uint16_t len)
{
int csr;
uint8_t i;
if (len < 6) return;
// Debug: print raw
PRINT("LUP Rx:");
for (i = 0; i < len; i++) {
PRINT(" %02X", pkg[i]);
}
PRINT("\n");
// --- Checksum ---
csr = lup_verify_checksum(pkg, len);
if (csr != 0) {
PRINT("LUP: checksum fail (%d)\n", csr);
return;
}
// --- Dispatch by CMD ---
uint8_t cmd = pkg[3];
// Active reports (0xC0) are NOT responses to commands
if (cmd == LUP_CMD_SENSOR_REPORT) {
// Sensor report — will be handled in upper layer (uart_srv + TCP JSON)
// Don't consume as command response
return;
}
// Try matching with pending command
lup_cmd_on_response(pkg, len);
}

View File

@@ -23,6 +23,7 @@
#include "net_srv.h" #include "net_srv.h"
#include "storage.h" #include "storage.h"
#include "tcp_json_srv.h" #include "tcp_json_srv.h"
#include "loop_uart_proto.h"
/********************************************************************* /*********************************************************************
* GLOBAL TYPEDEFS * GLOBAL TYPEDEFS
@@ -269,6 +270,8 @@ void Main_Circulation(void)
poll_dbn_ble(); poll_dbn_ble();
tcp_json_push_sensor(); // Push 0xC0 sensor data to TCP JSON client
tcp_json_poll(); tcp_json_poll();
key_event_srv(); key_event_srv();
@@ -335,6 +338,13 @@ void TIM3_IRQHandler(void)
} }
} }
} }
// Safety: reset frame parser if stuck in non-IDLE state too long
if(g_lup_parser.state != LUP_FRAME_STATE_IDLE){
if(g_pkg_uart_2.tick > 20){
lup_frame_reset();
g_pkg_uart_2.tick = 0;
}
}
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0){//HAL_PUSH_BUTTON()){ if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0){//HAL_PUSH_BUTTON()){
g_flag_counter_key.tick++; g_flag_counter_key.tick++;

View File

@@ -15,6 +15,7 @@
#include "wchnet.h" #include "wchnet.h"
#include "net_srv.h" #include "net_srv.h"
#include "cmcng.h" #include "cmcng.h"
#include "loop_uart_proto.h"
#include "simple_json.h" #include "simple_json.h"
#include "storage.h" #include "storage.h"
#include <string.h> #include <string.h>
@@ -29,6 +30,7 @@ TcpJsonAuthState g_json_auth_state = JSON_STATE_WAIT_AUTH;
uint32_t g_json_auth_timer = 0; uint32_t g_json_auth_timer = 0;
uint8_t g_json_pwd_retry = 0; uint8_t g_json_pwd_retry = 0;
uint32_t g_json_lockout_timer = 0; uint32_t g_json_lockout_timer = 0;
TcpJsonPending g_json_pending = {0, 0, 0, "", 0};
/*=========================================================================== /*===========================================================================
* Frame Receive Buffer (line-delimited JSON) * Frame Receive Buffer (line-delimited JSON)
@@ -464,46 +466,87 @@ static void handle_device_reset(uint8_t socket, uint32_t msg_id, const char *jso
NVIC_SystemReset(); NVIC_SystemReset();
} }
/* 4.13 loop_param_set */ /* 4.13 loop_param_set — 设置地感线圈多路参数 (CMD 0x63) */
static void handle_loop_param_set(uint8_t socket, uint32_t msg_id, const char *json) { static void handle_loop_param_set(uint8_t socket, uint32_t msg_id, const char *json) {
// Forward to Loop MCU via UART2
// Store params locally for query response
char *data = json_get_data_str(json); char *data = json_get_data_str(json);
if (!data) { if (!data) {
json_send_error(socket, msg_id, "loop_param_set", JSON_CODE_PARAM_ERR, "missing data"); json_send_error(socket, msg_id, "loop_param_set", JSON_CODE_PARAM_ERR, "missing data");
return; return;
} }
LUP_ParamSet ps;
memset(&ps, 0, sizeof(ps));
ps.auto_mode = (uint8_t)json_get_uint_field(data, "\"auto_mode\"");
// Parse channels array
// Use a simple approach: parse top-level fields for each known channel key
uint32_t amount = json_get_uint_field(data, "\"amount\"");
if (amount == 0 || amount > LUP_COIL_COUNT) {
amount = LUP_COIL_COUNT;
}
ps.amount = (uint8_t)amount;
// Parse per-channel data
char ch_key[16];
uint8_t ch;
for (ch = 0; ch < ps.amount; ch++) {
snprintf(ch_key, sizeof(ch_key), "\"ch%d\"", ch + 1);
char ch_data[512] = {0};
reset_tmp();
simple_parse_json(data, ch_key, g_tmp_value);
if (strlen(g_tmp_value) == 0) continue;
strncpy(ch_data, g_tmp_value, sizeof(ch_data) - 1);
LUP_CoilParam *cp = &ps.params[ch];
// sensitivity: 低4位=灵敏度, 高4位=freq_level
uint32_t sens_val = json_get_uint_field(ch_data, "\"sensitivity\"");
uint32_t freq_lev = json_get_uint_field(ch_data, "\"freq_level\""); // 0~3
cp->sensitivity = ((uint8_t)(freq_lev & 0x03) << 4) | (uint8_t)(sens_val & 0x0F);
cp->loop_delay = (uint8_t)json_get_uint_field(ch_data, "\"loop_delay\"");
// output_mode: 低3位=输出方式, 高5位=SafeMode
uint32_t out_mode = json_get_uint_field(ch_data, "\"output_mode\"");
uint32_t safe_mode = json_get_uint_field(ch_data, "\"safe_mode\"");
cp->output_mode = ((uint8_t)(safe_mode & 0x1F) << 3) | (uint8_t)(out_mode & 0x07);
cp->exist_mode = (uint8_t)json_get_uint_field(ch_data, "\"exist_mode\"");
// direction_mode: 低3位=方向模式, 高4位=Function_Mode
uint32_t dir_mode = json_get_uint_field(ch_data, "\"direction_mode\"");
uint32_t func_mode = json_get_uint_field(ch_data, "\"function_mode\"");
cp->direction_mode = ((uint8_t)(func_mode & 0x0F) << 4) | (uint8_t)(dir_mode & 0x07);
}
free(data); free(data);
// Build binary packet for Loop MCU (protocol 0x63 = CMD_SET_MCJQ_PARAM) // Send to Loop MCU and defer response
// For now, ack the command — full loop MCU integration deferred lup_send_set_param(&ps);
json_send_ok(socket, msg_id, "loop_param_set", NULL);
PRINT("JSON: loop_param_set (Loop MCU relay pending)\n"); // Save pending state for async response
g_json_pending.active = 1;
g_json_pending.socket = socket;
g_json_pending.msg_id = msg_id;
strncpy(g_json_pending.cmd, "loop_param_set", sizeof(g_json_pending.cmd) - 1);
g_json_pending.deadline = mstick() + 500;
PRINT("JSON: loop_param_set sent (ch=%d)\n", ps.amount);
} }
/* 4.14 loop_param_query */ /* 4.14 loop_param_query — 读取地感线圈多路参数 (CMD 0x64) */
static void handle_loop_param_query(uint8_t socket, uint32_t msg_id, const char *json) { static void handle_loop_param_query(uint8_t socket, uint32_t msg_id, const char *json) {
// Query Loop MCU via UART2 (protocol 0x64 = CMD_GET_MCJQ_PARAM) // Send query to Loop MCU and defer response
// For now, return stub data — full loop MCU integration deferred lup_send_get_param();
char data_json[600];
snprintf(data_json, sizeof(data_json),
"{\"auto_mode\":false,"
"\"channels\":["
"{\"ch\":1,\"sensitivity\":7,\"freq_level\":\"high\",\"loop_delay\":0,"
"\"output_mode\":\"exist\",\"exist_mode\":0,\"direction_mode\":0,"
"\"safe_mode\":0,\"function_mode\":0,\"freq_initial\":0,\"freq_current\":0,\"freq_diff\":0},"
"{\"ch\":2,\"sensitivity\":7,\"freq_level\":\"mid_high\",\"loop_delay\":5,"
"\"output_mode\":\"exist\",\"exist_mode\":0,\"direction_mode\":0,"
"\"safe_mode\":0,\"function_mode\":0,\"freq_initial\":0,\"freq_current\":0,\"freq_diff\":0},"
"{\"ch\":3,\"sensitivity\":5,\"freq_level\":\"mid_low\",\"loop_delay\":0,"
"\"output_mode\":\"exist\",\"exist_mode\":0,\"direction_mode\":0,"
"\"safe_mode\":0,\"function_mode\":0,\"freq_initial\":0,\"freq_current\":0,\"freq_diff\":0},"
"{\"ch\":4,\"sensitivity\":8,\"freq_level\":\"low\",\"loop_delay\":0,"
"\"output_mode\":\"exist\",\"exist_mode\":0,\"direction_mode\":0,"
"\"safe_mode\":0,\"function_mode\":0,\"freq_initial\":0,\"freq_current\":0,\"freq_diff\":0}]}");
json_send_ok(socket, msg_id, "loop_param_query", data_json); g_json_pending.active = 1;
PRINT("JSON: loop_param_query (stub — Loop MCU relay pending)\n"); g_json_pending.socket = socket;
g_json_pending.msg_id = msg_id;
strncpy(g_json_pending.cmd, "loop_param_query", sizeof(g_json_pending.cmd) - 1);
g_json_pending.deadline = mstick() + 500;
PRINT("JSON: loop_param_query sent\n");
} }
/* 4.15 report_config */ /* 4.15 report_config */
@@ -679,6 +722,169 @@ void tcp_json_handle_sock_int(uint8_t socketid, uint8_t intstat) {
} }
} }
/*===========================================================================
* Deferred Response Helpers
*===========================================================================*/
/*
* Format coil param query response as JSON
*/
static int format_loop_param_json(char *buf, uint16_t buf_size, const LUP_ParamGet *pg) {
const char *freq_level_names[] = {"high", "mid_high", "mid_low", "low"};
char *p = buf;
int remaining = buf_size;
int written;
uint8_t i;
written = snprintf(p, remaining,
"{\"auto_mode\":%s,\"amount\":%d,\"channels\":[",
pg->auto_mode ? "true" : "false", pg->amount);
if (written < 0 || written >= remaining) return -1;
p += written; remaining -= written;
for (i = 0; i < pg->amount; i++) {
const LUP_CoilParam *cp = &pg->params[i];
uint8_t freq_lev = (cp->sensitivity >> 4) & 0x03;
uint8_t sens = cp->sensitivity & 0x0F;
uint8_t out_mode = cp->output_mode & 0x07;
uint8_t safe_mod = (cp->output_mode >> 3) & 0x1F;
uint8_t dir_mode = cp->direction_mode & 0x07;
uint8_t func_mod = (cp->direction_mode >> 4) & 0x0F;
written = snprintf(p, remaining,
"%s{\"ch\":%d,\"sensitivity\":%d,\"freq_level\":\"%s\","
"\"loop_delay\":%d,\"output_mode\":%d,\"safe_mode\":%d,"
"\"exist_mode\":%d,\"direction_mode\":%d,\"function_mode\":%d,"
"\"freq_initial\":%lu,\"freq_current\":%lu,\"freq_diff\":%lu}",
(i > 0) ? "," : "",
i + 1, sens, freq_level_names[freq_lev],
cp->loop_delay, out_mode, safe_mod,
cp->exist_mode, dir_mode, func_mod,
pg->freq[i][0], pg->freq[i][1], pg->freq[i][2]);
if (written < 0 || written >= remaining) return -1;
p += written; remaining -= written;
}
written = snprintf(p, remaining, "]}");
if (written < 0 || written >= remaining) return -1;
return 0;
}
/*
* Format sensor report as JSON
*/
static int format_sensor_json(char *buf, uint16_t buf_size, const LUP_SensorReport *sr) {
const char *freq_level_names[] = {"high", "mid_high", "mid_low", "low"};
char *p = buf;
int remaining = buf_size;
int written;
uint8_t i;
written = snprintf(p, remaining,
"{\"sens_type\":\"multi_coil\",\"sub_amount\":%d,\"sub_seq\":%d,\"coils\":[",
sr->sub_amount, sr->sub_sequence);
if (written < 0 || written >= remaining) return -1;
p += written; remaining -= written;
for (i = 0; i < sr->coil_count; i++) {
const LUP_CoilSensor *cs = &sr->coils[i];
const char *misc_field;
uint32_t misc_val;
switch (cs->misc_type) {
case 0: misc_field = "\"passtime_ms5\""; misc_val = cs->misc.passtime_ms5; break;
case 1: misc_field = "\"cut_amount\""; misc_val = cs->misc.cut_amount; break;
case 2: misc_field = "\"flow_amount\""; misc_val = cs->misc.flow_amount; break;
default: misc_field = "\"misc\""; misc_val = 0; break;
}
written = snprintf(p, remaining,
"%s{\"ch\":%d,\"freq_level\":\"%s\",\"direction\":%d,"
"\"freq_type\":\"%s\",\"sensitivity\":%d,"
"\"condition\":%d,\"loop_state\":\"%s\",\"car_state\":\"%s\","
"\"freq\":%lu,\"variation\":%d,%s:%lu}",
(i > 0) ? "," : "",
i + 1,
freq_level_names[cs->freq_level],
cs->direction,
cs->freq_type ? "current" : "initial",
cs->sensitivity,
cs->condition,
cs->loop_state ? "broken" : "normal",
cs->car_state ? "car" : "no_car",
cs->freq,
cs->variation,
misc_field, misc_val);
if (written < 0 || written >= remaining) return -1;
p += written; remaining -= written;
}
written = snprintf(p, remaining, "]}");
if (written < 0 || written >= remaining) return -1;
return 0;
}
/*
* Check for and handle pending Loop MCU deferred responses
*/
static void json_check_pending(void) {
if (!g_json_pending.active) return;
uint8_t cmd = g_lup_cmd.pending_cmd;
if (g_lup_cmd.state == LUP_STATE_RESPONSE_READY) {
// Response received!
if (strcmp(g_json_pending.cmd, "loop_param_set") == 0) {
uint8_t success = 0;
int ret = lup_parse_set_param_resp(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &success);
if (ret == 0 && success) {
json_send_ok(g_json_pending.socket, g_json_pending.msg_id,
g_json_pending.cmd, NULL);
} else {
json_send_error(g_json_pending.socket, g_json_pending.msg_id,
g_json_pending.cmd, JSON_CODE_INTERNAL_ERR,
ret == 0 ? "Loop MCU returned failure" : "Parse error");
}
} else if (strcmp(g_json_pending.cmd, "loop_param_query") == 0) {
LUP_ParamGet pg;
memset(&pg, 0, sizeof(pg));
int ret = lup_parse_param_get(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &pg);
if (ret == 0) {
char data_json[2048];
if (format_loop_param_json(data_json, sizeof(data_json), &pg) == 0) {
json_send_ok(g_json_pending.socket, g_json_pending.msg_id,
g_json_pending.cmd, data_json);
} else {
json_send_error(g_json_pending.socket, g_json_pending.msg_id,
g_json_pending.cmd, JSON_CODE_INTERNAL_ERR,
"Response too large");
}
} else {
json_send_error(g_json_pending.socket, g_json_pending.msg_id,
g_json_pending.cmd, JSON_CODE_INTERNAL_ERR,
"Failed to parse Loop MCU response");
}
}
lup_cmd_done();
g_json_pending.active = 0;
PRINT("JSON: deferred response sent for %s\n", g_json_pending.cmd);
}
else if (g_lup_cmd.state == LUP_STATE_TIMEOUT
|| mstick() > g_json_pending.deadline) {
// Timeout
json_send_error(g_json_pending.socket, g_json_pending.msg_id,
g_json_pending.cmd, JSON_CODE_BUSY,
"Loop MCU no response");
lup_cmd_done();
g_json_pending.active = 0;
PRINT("JSON: deferred response timeout for %s\n", g_json_pending.cmd);
}
}
/*===========================================================================
* tcp_json_poll (updated)
*===========================================================================*/
void tcp_json_poll(void) { void tcp_json_poll(void) {
// Only poll if we have an active connection (socket configured) // Only poll if we have an active connection (socket configured)
if (g_json_socket_listen == 0xFF) return; if (g_json_socket_listen == 0xFF) return;
@@ -702,4 +908,50 @@ void tcp_json_poll(void) {
g_json_pwd_retry = 0; g_json_pwd_retry = 0;
} }
} }
// Check for pending Loop MCU deferred responses
json_check_pending();
}
/*===========================================================================
* tcp_json_push_sensor — Push sensor data (0xC0) to connected TCP client
*
* Called from main loop when uart_srv() has a sensor report frame in
* g_pkg_uart_2 that hasn't been consumed by BLE.
*===========================================================================*/
void tcp_json_push_sensor(void) {
// Check: socket active, authed, and data available
if (g_json_socket_listen == 0xFF) return;
if (g_json_auth_state != JSON_STATE_AUTHED) return;
// Only proceed if g_pkg_uart_2 has data and it's a 0xC0 sensor report
if (g_pkg_uart_2.flag == 0) return;
if (g_pkg_uart_2.pkg[0] != 0x7F) return;
if (g_pkg_uart_2.pkg[3] != 0xC0) return;
LUP_SensorReport sr;
memset(&sr, 0, sizeof(sr));
int ret = lup_parse_sensor_report(g_pkg_uart_2.pkg, g_pkg_uart_2.offset, &sr);
if (ret != 0) {
PRINT("JSON: sensor parse failed (%d)\n", ret);
InitPkgUart(&g_pkg_uart_2);
return;
}
char data_json[2048];
if (format_sensor_json(data_json, sizeof(data_json), &sr) == 0) {
char *out = (char *)malloc(TCP_JSON_MAX_FRAME);
if (out) {
snprintf(out, TCP_JSON_MAX_FRAME,
"{\"msg_id\":0,\"cmd\":\"sensor_report\",\"ts\":%lu,"
"\"code\":0,\"msg\":\"success\",\"data\":%s}\n",
(unsigned long)mstick(), data_json);
uint32_t slen = strlen(out);
WCHNET_SocketSend(g_json_socket_listen, (uint8_t *)out, &slen);
free(out);
}
}
// Clear after processing
InitPkgUart(&g_pkg_uart_2);
} }

View File

@@ -3,10 +3,12 @@
* *
* Created on: 2026-02-26 * Created on: 2026-02-26
* Author: wangfq * Author: wangfq
* Updated: 2026-07-02 — 使用 loop_uart_proto 帧解析器替代 timeout heuristic
*/ */
#include "config.h" #include "config.h"
#include "cmcng.h" #include "cmcng.h"
#include "loop_uart_proto.h"
#include <string.h> #include <string.h>
#include "dbn_ble_srv.h" #include "dbn_ble_srv.h"
@@ -34,7 +36,7 @@ void uart_init(void){
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // Rx GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // Rx
GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 192000;//115200; USART_InitStructure.USART_BaudRate = 115200; // 115200 (协议规定)
USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_Parity = USART_Parity_No;
@@ -42,7 +44,7 @@ void uart_init(void){
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART2, &USART_InitStructure); USART_Init(USART2, &USART_InitStructure);
// USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); // 配合DMA但测试不能接收原因未知。 // USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
@@ -55,11 +57,13 @@ void uart_init(void){
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//GPIO_Mode_IPU; // Key GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_Init(GPIOA, &GPIO_InitStructure);
} // 初始化帧解析器
lup_frame_reset();
}
@@ -76,11 +80,11 @@ void USART1_IRQHandler(void)
} }
/********************************************************************* /*********************************************************************
* @fn USART2_IRQHandler * @fn USART2_IRQHandler
* *
* @brief This function handles USART2 global interrupt request. * @brief USART2 RX — 使用 lup_feed_byte() 帧解析器
* 当解析出完整帧时,复制到 g_pkg_uart_2.pkg 并设置 flag
* *
* @return none * @return none
*/ */
@@ -88,25 +92,24 @@ void USART2_IRQHandler(void)
{ {
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{ {
uint8_t _dat = USART_ReceiveData(USART2); uint8_t _dat = USART_ReceiveData(USART2);
if(g_pkg_uart_2.offset == 0){
if(_dat != 0){
g_pkg_uart_2.pkg[g_pkg_uart_2.offset++] = _dat;
}
}
else{
if(g_pkg_uart_2.offset < BUFF_STACK_SIZE){
g_pkg_uart_2.pkg[g_pkg_uart_2.offset++] = _dat;
}
else{
g_pkg_uart_2.flag = 1;
}
}
if(g_pkg_uart_2.offset){ // 喂给帧解析器
if (lup_feed_byte(_dat)) {
// 帧接收完成,复制到 g_pkg_uart_2
const uint8_t *frame = lup_frame_data();
uint16_t frame_len = lup_frame_len();
if (frame_len <= BUFF_STACK_SIZE) {
memcpy(g_pkg_uart_2.pkg, frame, frame_len);
g_pkg_uart_2.offset = frame_len;
g_pkg_uart_2.flag = 1;
g_pkg_uart_2.tick = 0;
}
lup_frame_reset();
} else if (g_lup_parser.state != LUP_FRAME_STATE_IDLE) {
// 正在接收帧中tick 归零
g_pkg_uart_2.tick = 0; g_pkg_uart_2.tick = 0;
} }
} }
} }
@@ -133,44 +136,65 @@ void UART1_SendString(uint8_t *buf, uint16_t len)
} }
/*
* uart_srv — 主循环中调用,处理已接收完整的 UART2 帧
*
* 处理流程:
* 1. 0x7F + 0xC0/0x0C → 传感器数据上报
* - 若无 BLE 连接 → 标记 `_report_flag`,在 TCP JSON 中处理
* - 若有 BLE 连接且 acs_enable → 转 0x8F 前缀发给 BLE
* 2. 0x7F + 其他 CMD → 响应帧,交给 lup_process_frame() 匹配挂起命令
* 3. 非 0x7F → 调试打印(可能是字符串等)
*/
void uart_srv(void) void uart_srv(void)
{ {
uint8_t i; uint8_t i;
uint8_t _report_flag = 0; uint8_t _report_flag = 0;
// 检查命令超时
lup_cmd_check_timeout();
if(g_pkg_uart_2.flag){ if(g_pkg_uart_2.flag){
if(g_flag_counter_ota.flag == 0){ if(g_flag_counter_ota.flag == 0){
if(g_pkg_uart_2.pkg[0] == 0x7F){ if(g_pkg_uart_2.pkg[0] == 0x7F){
if(g_pkg_uart_2.pkg[3] == 0xC0 && g_pkg_uart_2.pkg[4] == 0x0C) uint8_t cmd = g_pkg_uart_2.pkg[3];
// --- 传感器上报 (0xC0) ---
if(cmd == 0xC0 && g_pkg_uart_2.pkg[4] == 0x0C)
{ {
if(g_dbn_ble_state_acs_enable.flag == 0){ if(g_dbn_ble_state_acs_enable.flag == 0){
// 无 BLE ACS 连接 → 标记为 TCP JSON 上报
_report_flag = 1; _report_flag = 1;
} }
else{ else{
// BLE ACS 已连接 → 改 Magic 为 0x8F 发给 BLE
g_pkg_uart_2.pkg[0] = 0x8F; g_pkg_uart_2.pkg[0] = 0x8F;
} }
} }
else {
// --- 命令响应 → 交给协议处理器 ---
lup_process_frame(g_pkg_uart_2.pkg, g_pkg_uart_2.offset);
}
// 调试打印
for(i = 0; i < g_pkg_uart_2.offset; i++){ for(i = 0; i < g_pkg_uart_2.offset; i++){
PRINT(" %02X", g_pkg_uart_2.pkg[i]); PRINT(" %02X", g_pkg_uart_2.pkg[i]);
} }
PRINT("\n"); PRINT("\n");
if(_report_flag){ if(_report_flag){
InitPkgUart(&g_pkg_uart_2); // 传感器帧保留在 pkg 中供上层 (tcp_json_srv) 处理
// 不 InitPkgUart — 由上层消费后再 InitPkgUart
} }
} }
else { else {
// 非 0x7F 魔法字节
PRINT("Rcv_len:%d,dat: %s\n", g_pkg_uart_2.offset, g_pkg_uart_2.pkg); PRINT("Rcv_len:%d,dat: %s\n", g_pkg_uart_2.offset, g_pkg_uart_2.pkg);
} }
} }
else{ else{
// PRINT("From_Loop: "); // OTA 模式 — 忽略 Loop MCU 数据
// for(i = 0; i < g_pkg_uart_2.offset; i++){
// PRINT(" %02X", g_pkg_uart_2.pkg[i]);
// }
// PRINT("\n");
} }
@@ -180,7 +204,11 @@ void uart_srv(void)
else{ else{
g_dbn_ble_state_acs_enable.flag = 0; g_dbn_ble_state_acs_enable.flag = 0;
} }
InitPkgUart(&g_pkg_uart_2);
// 只有非 _report_flag 时才立即清空
// _report_flag 的帧由 tcp_json 消费后清理
if (!_report_flag) {
InitPkgUart(&g_pkg_uart_2);
}
} }
} }

View File

@@ -0,0 +1,237 @@
# DLD960Loop 串口通信协议
# 1 通信说明
## 1.1 通信方式
串口通信波特率115200
# 2 协议格式
Package
| 1Byte | 3Bytes | 0~64Bytes | 2Bytes |
| --- | --- | --- | --- |
| Magic byte | Header | Value | Check Bytes |
## 2.1 Magic Byte(1Byte)
标识协议类型,也表示数据包开始信息。
| Magic Byte | 说明 |
| --- | --- |
| 0x7F | 屏、地感检测、探头的配置和查询协议,或者部分雷达的协议 |
| 0x8F | |
| 0x9F | OTA 用途 |
## 2.2 Header(3Bytes)
当Magic Byte为 0x7F时Header字节定义如下
| 1Byte | 1Byte | 1Byte |
| --- | --- | --- |
| Addr 地址 | LEN 数据长度 | CMD 命令字节 |
* Addr 默认0
* LEN数据长度表示数据包的有效数据(Value)长度,包括命令(CMD Byte)和数据(Data Bytes)
* CMD 命令字节
## 2.3 校验字节(Check Bytes2Bytes)
当Magic Byte为0x7F时校验字节实现如下
| 1Byte | 1Byte |
| --- | --- |
| XOR 异或校验 | SUM 和校验 |
> XOR 异或校验校验从Addr字节开始到命令数据的最后一个字节结束
> SUM 和校验校验从Addr字节开始到命令数据的最后一个字节结束。
# 3 Cmd详解
## 3.01 获取设备版本号(0x4A)
| | Header | Data |
| --- | --- | --- |
| **Value(Hex)** | 00 01 4A | |
| **Length** | 3 Byte | 0 Byte |
> eg: `7F 00 01 4A 4B 4B`
返回:
| | Header | Status + Hard\_Main + Hard\_Sub + Hard\_SSub + Soft\_Main + Soft\_Sub + Soft\_SSub |
| --- | --- | --- |
| **Value(Hex)** | 00 08 4A | xx |
| **Length** | 3 Byte | 7 Byte |
## 3.02 设备复位(0x6D)
| | Header | Data |
| --- | --- | --- |
| **Value(Hex)** | 00 01 6D | |
| **Length** | 3 Byte | 0 Byte |
设备无回复。
> eg:  `7F 00 01 6D 6C 6E`
## 3.03 出厂初始化(0x92)
| | Header | Data |
| --- | --- | --- |
| **Value(Hex)** | 00 01 92 | |
| **Length** | 3 Byte | 0 Byte |
> eg: `7F 00 01 92 93 93`
返回:
| | Header | Data |
| --- | --- | --- |
| **Value(Hex)** | 00 02 92 | 00/01(00 success) |
| **Length** | 3 Byte | 1 Byte |
> eg: `7F 00 02 92 00 90 94`
## 3.04 读写线圈灵敏度列表0x8A
| | Header | R/WAmount Amount \* (SensityIn + SensityOut) |
| --- | --- | --- |
| **Value(Hex)** | 00 04 8A | xx |
| **Length** | 3 Byte | (2 + 2\*x )Byte |
> R/W: 1字节0 Read 1 Write
> Amount1字节灵敏度值的数量 当R/W为 Write时有效最小3
> SensityIn两字节触发灵敏度值当R/W为 Write时有效
> SensityOut两字节释放灵敏度值当R/W为 Write时有效
返回
| | Header | Amount,  Amount \* SensityValue |
| --- | --- | --- |
| **Value(Hex)** | ADDR 03 8A | xx |
| **Length** | 3 Byte | (1 + 2\*x) Byte |
## 3.05 设置车检器多路参数(0x63)
| | Header | AutoMode + Amount + Param\[Sensitivity, Loop_Delay, Output_Mode, Exist_Mode, Direction_Mode\] |
| --- | --- | --- |
| **Value(Hex)** | 00 xx 63 | xx |
| **Length** | 3 Byte | (2 + 5 \* Amount) Byte |
* AutoMode: 是否处于自动模式自动调频0表示不启用默认0。
* Sensitivity 低四位表示灵敏度0~9级值越大灵敏度越高默认7高四位表示高低频其中\[4,5\]表示线圈的高低频。
* Loop\_Delay: 地感延时时间0~200 0.1s为一级,延时时间范围:\[0, 20秒\]
* Output\_Mode 低三位有效地感输出方式0 - 存在输出1-进入脉冲2-离开脉冲3-方向判别高5位表示SafeMode是否开启安全模式 0 关闭 0 开启安全模式分钟为单位。
* Exist\_Mode低四位表示地感存在方式0-永久存在非0-有限存在的分钟数;
* Direction\_Mode:
* 低三位表示方向判别模式0 表示触发模式 1-6 表示方向判别的输出模式对应以前的 0-5,   update 2023-02-24
* 高四位表示Function\_Mode功能模式是否启用特殊的模式
| | Header | Status |
| --- | --- | --- |
| **Value(Hex)** | 00 02 63 | 00/01 |
| **Length** | 3 Byte | 1 Byte |
## 3.06 读取车检器多路参数(0x64)
| | Header | Param |
| --- | --- | --- |
| **Value(Hex)** | 00 01 64 | |
| **Length** | 3 Byte | |
返回:
| | Header | AutoMode+Amount + Data\[Sensitivity, Loop_Delay, Output_Mode, Exist_Mode, Direction_Mode, freq1,  freq2, freq3\] |
| --- | --- | --- |
| **Value(Hex)** | 00 xx 64 | XX |
| **Length** | 3 Byte | (2 + 8 \* Amount) Byte |
## 3.07 设备主动上报传感信息(0xC0)
设备主动上报传感信息指令,具体传感数据需要根据不同的类型进行解析。
| 7Fh | ADDR | Len | C0 | SensType(1Byte) | SubPkgFlag(1Byte) | SensData | XOR | SUM |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
SensType: 传感数据类型
SubPkgFlag: 分包标记 SubPkgFlag高四位表示分包数量 SubAmount低四位表示为当前分包中第几个分包 SubSequence其中 SubSequence = \[1,SubAmount\]
* 00 表示无分包是一条数据完整的记录
* 当SubAmount不为0时SubSequence从1开始到SubAmount结束整合为一条完整的数据包。
* 当SensType为0x05时SubPkgFlag作为传感数据使用。
* 当SensType为0x06、0x07、0x09时SubPkgFlag的Seq和SubAmount分开各用一个字节表示。
SensData传感数据
| SensType | 类型说明 | SensData | 传感数据说明 |
| --- | --- | --- | --- |
| 0x0C | 多线圈传感信息 | | 四路线圈 |
| | | | |
上报线圈传感数据单元(多路线圈,每一路线圈单元的配置)
| 字段 | 字节数 | 内容 | 详情 |
| --- | --- | --- | --- |
| 配置 1 | 1 | freq\_level(2bit), Direction(1bit)freq\_type(1bit), sens(4bit) | 线圈 1 配置freq\_level:高低频两个比特位00 表示高频(33nF),01表示中高(43nF), 10表示中低(66nF), 11表示低频(76nF)。Direction: 0 表示触发1 表示方向判别freq\_type: 0 表示初始频率1 表示当前实时频率。sens低四位表示当前的灵敏度等级 |
| 线圈评估条件 | 1 | condition(4bit), loop\_state(1bit),<br>car\_state(1bit),<br>report\_msic(2bit) | loop\_condition 正常0有效环境状态条件环境状态评估值值越大干扰越大高四位有效。低四位中 其中第3位表示线圈状态0 表示正常1 表示线圈断开 2 位表示有无车0 表示无车1 表示有车低两位表示**杂项类型**0b00表示时间量0b01表示线圈断开次数0b10表示车流量数 |
| 频率 1 | 3 | frequent | 低字节在前 |
| 变化量 1 | 2 | variation | 低字节在前 |
| 杂项 | 4 | in\_out\_passtime/cut\_amount/flow\_amount | **杂项类型**,可能是时间量,也可能是线圈断开次数、车流量数,说明:<br>1、通过时间或车间距5ms为单位最大计时<br>2、线圈断开次数<br>3、车流量数 |
eg:
`7F 00 2F C0 0C 00 12 00 2A 0F 01 07 00 00 00 00 00 D2 00 75 B7 00 01 00 00 00 00 00 12 00 EC 0C 01 02 00 00 00 00 00 92 00 83 C3 00 02 00 00 00 00 00 E2 34`
指令解释
```python
7F Magic Byte
00 Addr0 default
2F Len, 0x2F=47个字节Cmd + Data
C0 Cmd
0C SensType传感数据类型0x0C 多线圈传感信息960Loop为 四路线圈车检器会有4个传感数据单元
00 SubPkgFlag分包标记00 表示无分包是一条数据完整的记录
12 00 2A 0F 01 07 00 00 00 00 00线圈1传感数据单元0b00 高频0b0 触发模式0b1实时频率0b0010 表示2级灵敏度0b0000环境正常0b0线圈正常0b0当前无车0b00 表示当前杂项参数为时间量0x010F2A频率为69418Hz0x0007变化量为70x00000000时间量为为0
D2 00 75 B7 00 01 00 00 00 00 00线圈2传感数据单元0b11 低频0b0 触发模式0b1实时频率0b0010 表示2级灵敏度0b0000环境正常0b0线圈正常0b0当前无车0b00 表示当前杂项参数为时间量0x00B775频率为46965Hz0x0001变化量为10x00000000时间量为为0
12 00 EC 0C 01 02 00 00 00 00 00线圈3传感数据单元0b00 高频0b0 触发模式0b1实时频率0b0010 表示2级灵敏度0b0000环境正常0b0线圈正常0b0当前无车0b00 表示当前杂项参数为时间量0x010CEC频率为68844Hz0x0002变化量为20x00000000时间量为为0
92 00 83 C3 00 02 00 00 00 00 00线圈4传感数据单元0b10 中低0b0 触发模式0b1实时频率0b0010 表示2级灵敏度0b0000环境正常0b0线圈正常0b0当前无车0b00 表示当前杂项参数为时间量0x00C383频率为50051Hz0x0002变化量为20x00000000时间量为为0
E2 34 校验字节
```
**注意,如果有续包,最后一包上位机无返回**。中间续包上位机返回传感类型,当前收到包的序列和子包的数量:
| 7Fh | ADDR | 07 | C0 | SensType | Seq(1Byte) | SubAmount(1Byte) | XOR | SUM |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
---
# 修订记录
| 版本 | 修订时间 | 修订说明 | 修订人 |
| --- | --- | --- | --- |
| | | | |
| V1.00 | 2026-02-11 | 创建协议初始版本 | wangfq |
| V1.01 | 2026-06-22 | 补充遗漏的接口说明 | wangfq |
| V1.02 | 2026-07-02 | 查缺补漏,补充例程说明。 | wangfq |