协议 V1.10 (3be9e50) 三处新增落地:
1. dev_info_query 响应 data 补 loop_ver/loop_hw_ver (0x4A 缓存值, 未完成/失败为空串)
2. initialize 上报同样携带 (尽力, 可为空)
3. 新增 §4.25 loop_version_query: 0x4A 异步实时查询, 响应在 iot_verq_poll() 回包
固件:
- loop_uart_proto.h/c: 新增 LoopVerCache + g_lup_ver_cache (0x4A 缓存);
lup_cache_from_info() 写缓存, lup_refresh_version_cache() 后台刷新 (通道空闲才发)
- iot_mqtt_srv.c: dev_info_query/initialize 加字段; loop_version_query 分支
(暂存 msg_id/deadline 300ms, busy 拒绝重复查询); iot_verq_poll() 异步回包
(RESPONSE_READY -> code=0 + 三字段 + 同步刷缓存; TIMEOUT -> code=5 保缓存值);
后台刷新: 上电 iot_mqtt_init + OTA done (iot_ota_report_result ok=1) 置 dirty
关键设计: 版本来源=0x4A 实时查询 (非 OTA 元数据 version=目标版本);
缓存值语义 dev_info_query/initialize 同步回包不等异步; 与 TCP 共用 g_lup_cmd
单命令通道 (后发覆盖, 先发者超时 — 低频命令, 待板上验证时序)
单测: tests/test_iot_loop_ver.c — gcc 隔离 (extract+embed 6 函数 + mock)
44 断言全过: 版本帧解析(正常+4 错误路径) / 缓存写入+格式化(空串/NULL 防护) /
verq_poll 成功回包(code=0+三字段+topic+缓存刷新+通道释放) / 超时回包(code=5+缓存值) /
后台刷新(发起/忙不消费/响应消费/残留超时清理)
验证: check_c_balance.py 3 文件 OK; 待板上验证 UART2 0x4A 与 loop_data 共用总线时序
857 lines
27 KiB
C
857 lines
27 KiB
C
/**
|
|
******************************************************************************
|
|
* @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 "fault_diag.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)
|
|
{
|
|
// Frame: [Magic][Addr][LEN][Value(LEN bytes)][XOR][SUM]
|
|
// Total = 1 + 1 + 1 + LEN + 2 = 5 + LEN
|
|
// (Addr + LEN_field are NOT inside Value; CMD is the 1st byte of Value)
|
|
uint8_t total_len = 5 + pkg[2]; // Magic(1)+Addr(1)+LEN(1)+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 03 8A 00 00 xx xx
|
|
* LEN=3: CMD(1) + R/W(1) + Amount(1)
|
|
*/
|
|
uint16_t lup_build_sensitivity_read(uint8_t *buf)
|
|
{
|
|
buf[0] = LUP_MAGIC;
|
|
buf[1] = LUP_ADDR_DEFAULT;
|
|
buf[2] = 0x03; // LEN = 3 (CMD + R/W + Amount)
|
|
buf[3] = LUP_CMD_SENSITIVITY; // 0x8A
|
|
buf[4] = LUP_SENS_RW_READ; // 0x00
|
|
buf[5] = 0x00; // Amount (ignored for read)
|
|
lup_append_checksum(buf);
|
|
return 5 + buf[2]; // = 8
|
|
}
|
|
|
|
/*
|
|
* 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 = CMD(1) + R/W(1) + Amount(1) + Amount*(SensIn(2)+SensOut(2))
|
|
// = 3 + sens->amount * 4
|
|
uint8_t val_len = 3 + sens->amount * 4;
|
|
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 5 + val_len; // Magic(1)+Addr(1)+LEN(1)+Value(val_len)+Check(2)
|
|
}
|
|
|
|
/*
|
|
* 0x63 — 设置多路参数
|
|
* Value: AutoMode(1) + Amount(1) + Amount * Param[5]
|
|
* LEN = CMD(1) + AutoMode(1) + Amount(1) + Amount*5 = 3 + 5*Amount
|
|
*/
|
|
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;
|
|
uint8_t val_len = 3 + 5 * ps->amount; // CMD + AutoMode + Amount + Amount*Param
|
|
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 5 + val_len; // Magic(1)+Addr(1)+LEN(1)+Value(val_len)+Check(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 + 3 padding)
|
|
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;
|
|
lup_append_checksum(buf);
|
|
return 5 + buf[2]; // = 12: Magic+Addr+LEN+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: Ret(1) + Amount(1) + Amount*(SensIn(2) + SensOut(2))
|
|
* Ret = 0x10 (Read return) | 0x11 (Write return)
|
|
*
|
|
* eg: 7F 80 13 8A 10 04 A2 00 51 00 51 00 36 00 ...
|
|
* Ret=0x10=Read, Amount=4
|
|
* Lv0: SensIn=0x00A2, SensOut=0x0051
|
|
*/
|
|
int lup_parse_sensitivity_resp(const uint8_t *pkg, uint16_t len, LUP_Sensitivity *sens)
|
|
{
|
|
uint8_t i;
|
|
if (len < 8) return -1; // min: 7F+Addr+LEN+CMD+Ret+Amount+XOR+SUM = 8
|
|
if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_SENSITIVITY) return -2;
|
|
|
|
const uint8_t *val = pkg + 4;
|
|
sens->rw = val[0]; // 0x10 or 0x11
|
|
sens->amount = val[1];
|
|
|
|
if (sens->amount > LUP_COIL_COUNT) sens->amount = LUP_COIL_COUNT;
|
|
|
|
for (i = 0; i < sens->amount; i++) {
|
|
sens->sens_in[i] = val[2 + i * 4] | ((uint16_t)val[3 + i * 4] << 8);
|
|
sens->sens_out[i] = val[4 + i * 4] | ((uint16_t)val[5 + i * 4] << 8);
|
|
}
|
|
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 — 多路线圈传感信息
|
|
*
|
|
* 每路线圈数据单元 = 12 bytes (V1.05):
|
|
* [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-7] 变化量 (3 bytes, LE, 有符号补码, = Origin - CAPVD)
|
|
* [8-11] 杂项 (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 * 12
|
|
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 / 12;
|
|
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 * 12;
|
|
|
|
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);
|
|
|
|
// 变化量 3B 有符号 (LE) — 读入后须符号扩展 bit23
|
|
{
|
|
int32_t v = c[5] | ((int32_t)c[6] << 8) | ((int32_t)c[7] << 16);
|
|
if (v & 0x800000) v |= (int32_t)0xFF000000;
|
|
cs->variation = v;
|
|
}
|
|
cs->misc.passtime_ms = c[8] | ((uint32_t)c[9] << 8)
|
|
| ((uint32_t)c[10] << 16) | ((uint32_t)c[11] << 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
|
|
*===========================================================================*/
|
|
|
|
static void lup_debug_tx(const uint8_t *buf, uint16_t len)
|
|
{
|
|
uint8_t i;
|
|
PRINT("LUP Tx:");
|
|
for (i = 0; i < len; i++) {
|
|
PRINT(" %02X", buf[i]);
|
|
}
|
|
PRINT("\n");
|
|
}
|
|
|
|
void lup_send_get_version(void)
|
|
{
|
|
uint8_t buf[LUP_MAX_PKG_LEN];
|
|
uint16_t len = lup_build_get_version(buf);
|
|
lup_debug_tx(buf, len);
|
|
UART2_SendString(buf, len);
|
|
lup_cmd_begin(LUP_CMD_GET_VERSION, 200);
|
|
}
|
|
|
|
/*===========================================================================
|
|
* 版本缓存 (V1.10) — 0x4A 查询结果缓存, 供 MQTT dev_info_query/initialize
|
|
*===========================================================================*/
|
|
LoopVerCache g_lup_ver_cache; /* BSS 清零: valid=0 */
|
|
|
|
void lup_cache_from_info(const LUP_VersionInfo *info)
|
|
{
|
|
if (!info) return;
|
|
g_lup_ver_cache.valid = 1;
|
|
g_lup_ver_cache.hard_main = info->hard_main;
|
|
g_lup_ver_cache.hard_sub = info->hard_sub;
|
|
g_lup_ver_cache.hard_ssub = info->hard_ssub;
|
|
g_lup_ver_cache.soft_main = info->soft_main;
|
|
g_lup_ver_cache.soft_sub = info->soft_sub;
|
|
g_lup_ver_cache.soft_ssub = info->soft_ssub;
|
|
}
|
|
|
|
/* 后台刷新: 仅命令通道空闲时发起 (g_lup_cmd 全局唯一, 与 TCP/MQTT 命令共用
|
|
单通道; 有命令在途则跳过本轮, 下次空闲再发) */
|
|
void lup_refresh_version_cache(void)
|
|
{
|
|
if (g_lup_cmd.state != LUP_STATE_IDLE) return;
|
|
lup_send_get_version();
|
|
}
|
|
|
|
void lup_send_reset(void)
|
|
{
|
|
uint8_t buf[LUP_MAX_PKG_LEN];
|
|
uint16_t len = lup_build_reset(buf);
|
|
lup_debug_tx(buf, len);
|
|
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);
|
|
lup_debug_tx(buf, len);
|
|
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);
|
|
lup_debug_tx(buf, len);
|
|
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);
|
|
lup_debug_tx(buf, len);
|
|
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);
|
|
lup_debug_tx(buf, len);
|
|
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;
|
|
default:
|
|
/* 未知状态 (含 0x9F OTA 专用状态值): 防御性复位, 防状态机跑飞 */
|
|
lup_frame_reset();
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*===========================================================================
|
|
* 0x9F OTA 帧解析器 (OTA 模式专用, 2026-08-19)
|
|
*
|
|
* 帧格式 (DLD960LoopBootloader 源码确认):
|
|
* [9F] [SubL] [SubH] [LEN] [CMD] [DATA: LEN-1] [CHECK: SUM]
|
|
* - SubL/SubH: 分包序号, 2 字节小端 (低字节在前)
|
|
* - LEN: 含 CMD 的字节数, 故 DATA = LEN - 1
|
|
* - CHECK: 仅 SUM 校验 (无 XOR), 从 SubL 累加到 DATA 最后字节
|
|
* 总帧长 = LEN + 5 (与 0x7F 相同)
|
|
*
|
|
* 状态机复用 g_lup_parser (缓冲/索引), 状态值独立 (LUP_FRAME_STATE_OTA_*)
|
|
* 调用方 (uart2_dma_poll) 在 OTA 模式切换时负责 lup_frame_reset()
|
|
*===========================================================================*/
|
|
int lup_feed_byte_ota(uint8_t byte)
|
|
{
|
|
LUP_FrameParser *p = &g_lup_parser;
|
|
|
|
switch (p->state) {
|
|
case LUP_FRAME_STATE_IDLE:
|
|
if (byte == LUP_MAGIC_OTA) {
|
|
p->buf[0] = byte;
|
|
p->idx = 1;
|
|
p->state = LUP_FRAME_STATE_HEADER; /* 复用 HEADER: 收 SubL/SubH */
|
|
}
|
|
break;
|
|
|
|
case LUP_FRAME_STATE_HEADER:
|
|
p->buf[p->idx++] = byte;
|
|
if (p->idx == 3) { /* SubL + SubH 收完 */
|
|
p->state = LUP_FRAME_STATE_OTA_LEN;
|
|
}
|
|
break;
|
|
|
|
case LUP_FRAME_STATE_OTA_LEN:
|
|
p->buf[p->idx++] = byte;
|
|
{
|
|
uint8_t len_field = p->buf[3];
|
|
uint16_t data_bytes;
|
|
if (len_field < 1) {
|
|
data_bytes = 0; /* LEN<1 非法, 按无数据直接到 CHECK */
|
|
} else {
|
|
data_bytes = len_field - 1; /* LEN 含 CMD */
|
|
}
|
|
if (data_bytes > LUP_MAX_VALUE_LEN) {
|
|
lup_frame_reset(); /* 非法长度, 丢弃 */
|
|
break;
|
|
}
|
|
p->value_len = data_bytes;
|
|
p->value_idx = 0;
|
|
}
|
|
p->state = LUP_FRAME_STATE_OTA_CMD;
|
|
break;
|
|
|
|
case LUP_FRAME_STATE_OTA_CMD:
|
|
p->buf[p->idx++] = byte;
|
|
if (p->value_len > 0) {
|
|
p->state = LUP_FRAME_STATE_VALUE; /* 复用 VALUE: 收 DATA */
|
|
} else {
|
|
p->state = LUP_FRAME_STATE_OTA_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_OTA_CHECK;
|
|
}
|
|
break;
|
|
|
|
case LUP_FRAME_STATE_OTA_CHECK:
|
|
p->buf[p->idx++] = byte;
|
|
{
|
|
/* SUM = SubL+SubH+LEN+CMD+DATA (不含 magic 0x9F, 不含本 CHECK) */
|
|
uint8_t sum = 0;
|
|
uint16_t i;
|
|
for (i = 1; i < (uint16_t)(p->idx - 1); i++) {
|
|
sum += p->buf[i];
|
|
}
|
|
if (sum == p->buf[p->idx - 1]) {
|
|
p->state = LUP_FRAME_STATE_COMPLETE;
|
|
return 1; /* 帧完成 */
|
|
}
|
|
lup_frame_reset(); /* 校验失败, 丢弃 */
|
|
}
|
|
break;
|
|
|
|
case LUP_FRAME_STATE_COMPLETE:
|
|
// 不应该到达这里,调用方应在 COMPLETE 后 reset
|
|
lup_frame_reset();
|
|
break;
|
|
|
|
default:
|
|
lup_frame_reset();
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*===========================================================================
|
|
* Sensor Report Callback
|
|
*===========================================================================*/
|
|
static lup_sensor_cb_t g_lup_sensor_cb = NULL;
|
|
|
|
void lup_set_sensor_callback(lup_sensor_cb_t cb)
|
|
{
|
|
g_lup_sensor_cb = cb;
|
|
PRINT("LUP: sensor callback %s\n", cb ? "registered" : "cleared");
|
|
}
|
|
|
|
/*
|
|
* 收到完整帧后的处理:
|
|
* 1. 校验 checksum
|
|
* 2. 如果是主动上报 (0xC0) → 调用回调(网络推送等)
|
|
* 3. 如果是命令响应 → 匹配挂起命令
|
|
*/
|
|
void lup_process_frame(const uint8_t *pkg, uint16_t len)
|
|
{
|
|
int csr;
|
|
uint8_t i;
|
|
|
|
FAULT_MARKER(MK_LUP_FRAME_IN);
|
|
|
|
if (len < 6) { FAULT_MARKER(MK_LUP_FRAME_OUT); return; }
|
|
|
|
// Debug: print raw (2026-08-13: 缓冲一次性输出 — 逐字节 PRINT 关中断 ~350us/字节
|
|
// 会屏蔽 UART2 ISR 丢帧, 且高频打印放大 printf 重入窗口。
|
|
// 再关闭: 190B 打印 @256000 = 7.4ms 关中断 → UART2(192000) 溢出丢字节 → 粘帧!
|
|
// 需要调试时打开 #if 1)
|
|
#if 0
|
|
{
|
|
char _hex[224];
|
|
int _p = snprintf(_hex, sizeof(_hex), "LUP Rx:");
|
|
for (i = 0; i < len && _p >= 0 && _p < (int)sizeof(_hex) - 5; i++) {
|
|
_p += snprintf(_hex + _p, sizeof(_hex) - (size_t)_p, " %02X", pkg[i]);
|
|
}
|
|
PRINT("%s\n", _hex);
|
|
}
|
|
#endif
|
|
|
|
// --- 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) → trigger callback for network/upstream forwarding
|
|
if (cmd == LUP_CMD_SENSOR_REPORT) {
|
|
if (g_lup_sensor_cb) {
|
|
g_lup_sensor_cb(pkg, len);
|
|
} else {
|
|
PRINT("LUP: 0xC0 received but no callback registered!\n");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Try matching with pending command
|
|
lup_cmd_on_response(pkg, len);
|
|
FAULT_MARKER(MK_LUP_FRAME_OUT);
|
|
}
|