diff --git a/apps/chrg/applications/thread/chrg_comm.c b/apps/chrg/applications/thread/chrg_comm.c index 504cdb6..445bb1d 100644 --- a/apps/chrg/applications/thread/chrg_comm.c +++ b/apps/chrg/applications/thread/chrg_comm.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_comm.c 完成日期 : -当前版本号 : V1.1.24 +当前版本号 : V1.1.26 主要功能 : 实现对外通讯协议 MODBUS 服务端,以独立线程等待处理响应请求数据。 版本历史 : 创建原始版本 说明 : @@ -2547,13 +2547,62 @@ static void chrg_comm_get_param_diff(eIDX_ID role, } } +/***************************************************************** +函数名称: chrg_comm_sink_config_equal +函数描述: 只比较PC可写配置,忽略影子区中过期的状态机和量测快照 +其它说明: 测试参数子结构仅包含配置;test_mode_old及执行步骤不参与比较 +*****************************************************************/ +static rt_bool_t chrg_comm_sink_config_equal(const struct chrg_switch_t *a, + const struct chrg_switch_t *b) +{ + return (a->CV_mode == b->CV_mode) && (a->protoCmd == b->protoCmd) && + (a->sink.protocol == b->sink.protocol) && + (a->sink.Pro_Group == b->sink.Pro_Group) && + (a->sink.pro_loadv == b->sink.pro_loadv) && + (a->sink.pro_loadc == b->sink.pro_loadc) && + (a->sink.cc_set == b->sink.cc_set) && + (a->sink.loadv == b->sink.loadv) && (a->sink.loadc == b->sink.loadc) && + (a->sink.volt_on == b->sink.volt_on) && + (a->sink.Now_State == b->sink.Now_State) && + (a->sink.test.test_mode == b->sink.test.test_mode) && + (a->sink.test.short_test.hold_time == b->sink.test.short_test.hold_time) && + (a->sink.test.short_test.set_mode == b->sink.test.short_test.set_mode) && + (a->sink.test.short_test.have_curr_flag == b->sink.test.short_test.have_curr_flag) && + (rt_memcmp(&a->sink.test.OCP_test, &b->sink.test.OCP_test, + sizeof(a->sink.test.OCP_test)) == 0) && + (rt_memcmp(&a->sink.test.change_test, &b->sink.test.change_test, + sizeof(a->sink.test.change_test)) == 0); +} + +/***************************************************************** +函数名称: chrg_comm_copy_sink_config +函数描述: 将PC配置逐项应用到Sink,保留实际运行状态、旧测试模式和量测 +其它说明: 禁止复制整个switch/sink/test,避免覆盖初始化及回码推进结果 +*****************************************************************/ +static void chrg_comm_copy_sink_config(struct chrg_switch_t *pReal, + const struct chrg_switch_t *pShadow) +{ + pReal->CV_mode = pShadow->CV_mode; + pReal->protoCmd = pShadow->protoCmd; + pReal->sink.protocol = pShadow->sink.protocol; + pReal->sink.Pro_Group = pShadow->sink.Pro_Group; + pReal->sink.pro_loadv = pShadow->sink.pro_loadv; + pReal->sink.pro_loadc = pShadow->sink.pro_loadc; + pReal->sink.cc_set = pShadow->sink.cc_set; + pReal->sink.loadv = pShadow->sink.loadv; + pReal->sink.loadc = pShadow->sink.loadc; + pReal->sink.volt_on = pShadow->sink.volt_on; + pReal->sink.Now_State = pShadow->sink.Now_State; + pReal->sink.test.test_mode = pShadow->sink.test.test_mode; + pReal->sink.test.short_test = pShadow->sink.test.short_test; + pReal->sink.test.OCP_test = pShadow->sink.test.OCP_test; + pReal->sink.test.change_test = pShadow->sink.test.change_test; +} + /***************************************************************** 函数名称: chrg_comm_apply_sink_effect -函数描述: 处理COM参数生效后的同角色负载启停、测试和差异更新 -输入参数: ch:通道 pReal:运行参数 pShadow:待生效影子参数 -输出参数: 更新pReal并按需提交南向命令 -返回说明: MB_EX_NONE:成功 MB_EX_SLAVE_BUSY:命令提交失败 -其它说明: 角色切换不在本函数处理,由chrg_mb_write_sys统一调度 +函数描述: 应用同角色负载配置;相同初始化请求继续等待,不取消退出Short批次 +返回说明: 初始化期间不同配置返回忙,PC需重试;停止请求不受此限制 *****************************************************************/ static eMBException chrg_comm_apply_sink_effect(rt_uint8_t ch, struct chrg_switch_t *pReal, const struct chrg_switch_t *pShadow) @@ -2574,6 +2623,25 @@ static eMBException chrg_comm_apply_sink_effect(rt_uint8_t ch, rt_uint16_t sink_work = pShadow->sink.Now_State; chrg_param_diff_t diff_info; + /* 初始化是一个完整事务。相同运行请求只推进原事务;不同参数必须 + * 等待完成后重试,禁止先部分应用新配置或清掉尚未确认的退出命令。 */ + if ((sink_work == 0x01) && + ((old_work_mode == SINK_WORK_INIT) || + (old_work_mode == SINK_WORK_INIT_AUX_WAIT))) { + if (!chrg_comm_sink_config_equal(pReal, pShadow)) { + return MB_EX_SLAVE_BUSY; + } + return (chrg_sink_work((eIDX_SOU_CH)ch, pReal) == 0) ? + MB_EX_NONE : MB_EX_SLAVE_BUSY; + } + /* 未完成安全停止前不能用新运行请求打断停止批次。 */ + if ((sink_work == 0x01) && + ((old_work_mode == SINK_WORK_STOP) || + (old_work_mode == SINK_WORK_STOP_WAIT) || + (old_work_mode == SINK_WORK_STOP_RELAY) || + (old_work_mode == SINK_WORK_STOP_RELAY_WAIT))) { + return MB_EX_SLAVE_BUSY; + } chrg_comm_get_param_diff(ID_SINK, pReal, pShadow, &diff_info); /* * 协议类型、CC和协议组号变化需要完整握手;协议电压和协议电流 @@ -2605,7 +2673,7 @@ static eMBException chrg_comm_apply_sink_effect(rt_uint8_t ch, return MB_EX_SLAVE_BUSY; } } - rt_memcpy(pReal, pShadow, sizeof(struct chrg_switch_t)); + chrg_comm_copy_sink_config(pReal, pShadow); if (sink_work == 0x01) { curr_test = pReal->sink.test.test_mode; @@ -2934,6 +3002,9 @@ static eMBException chrg_mb_write_sys(rt_uint8_t ch, rt_uint16_t reg_offset, struct chrg_thread_t *pTHR_NOR; eIDX_ID old_id; eIDX_ID new_id; + rt_bool_t sink_config_changed; + rt_uint8_t old_sink_sub; + rt_uint8_t old_sink_on; int switch_ret; (void)pSOU; @@ -2956,13 +3027,8 @@ static eMBException chrg_mb_write_sys(rt_uint8_t ch, rt_uint16_t reg_offset, old_id = pReal->id; new_id = pSW->id; g_shadow_dirty[ch] = 0; - /* - * 使每次COM参数生效拥有独立版本。北向线程等待回码期间如果版本变化, - * 必须放弃旧请求,防止旧回码把新配置的sub错误推进到下一步。 - */ - pNOR->config_version[ch]++; - if (old_id != new_id) { + pNOR->config_version[ch]++; /* 角色切换时清除旧Source协议切换的等待上下文。 */ chrg_roll_nor_source_switch_reset(ch); rt_memcpy(pReal, pSW, sizeof(struct chrg_switch_t)); @@ -2977,8 +3043,19 @@ static eMBException chrg_mb_write_sys(rt_uint8_t ch, rt_uint16_t reg_offset, } status = (switch_ret == 0) ? MB_EX_NONE : MB_EX_SLAVE_BUSY; } else if (new_id == ID_SINK) { + sink_config_changed = !chrg_comm_sink_config_equal(pReal, pSW); + old_sink_sub = pReal->sub; + old_sink_on = pReal->sink.On_work; status = chrg_comm_apply_sink_effect(ch, pReal, pSW); + /* 重复请求或初始化期间被拒绝的新配置不能作废正在等待的北向 + * 回码;只有配置确实应用或控制入口改变时才递增版本。 + * 即使南向暂时入队失败,已接受的新配置也必须隔离旧回码。 */ + if ((sink_config_changed && chrg_comm_sink_config_equal(pReal, pSW)) || + (old_sink_sub != pReal->sub) || (old_sink_on != pReal->sink.On_work)) { + pNOR->config_version[ch]++; + } } else { + pNOR->config_version[ch]++; status = chrg_comm_apply_source_effect(ch, pReal, pSW); } diff --git a/apps/chrg/applications/thread/chrg_roll_nor.c b/apps/chrg/applications/thread/chrg_roll_nor.c index eb31021..a3ff97d 100644 --- a/apps/chrg/applications/thread/chrg_roll_nor.c +++ b/apps/chrg/applications/thread/chrg_roll_nor.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_roll_nor.c 完成日期 : -当前版本号 : V1.1.24 +当前版本号 : V1.1.26 主要功能 : 实现北向通信轮巡请求命令。以独立线程处理。 版本历史 : 创建原始版本 说明 : @@ -1016,6 +1016,8 @@ void chrg_roll_nor_thread_entry (void *data) } else if ((pSW->id == ID_SINK) && ((pSW->sink.work_mode == SINK_WORK_INIT) || (pSW->sink.work_mode == SINK_WORK_INIT_AUX_WAIT) || + /* STOP入队失败时On_work已清零,必须在使能过滤前继续重试。 */ + (pSW->sink.work_mode == SINK_WORK_STOP) || (pSW->sink.work_mode == SINK_WORK_UNLOAD_WAIT) || (pSW->sink.work_mode == SINK_WORK_LOW) || (pSW->sink.work_mode == SINK_WORK_LOW_AUX_WAIT) || diff --git a/apps/chrg/applications/utils/chrg_def.h b/apps/chrg/applications/utils/chrg_def.h index 554b026..8dad0de 100644 --- a/apps/chrg/applications/utils/chrg_def.h +++ b/apps/chrg/applications/utils/chrg_def.h @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_def.h 完成日期 : -当前版本号 : V1.1.24 +当前版本号 : V1.1.26 主要功能 : 全局宏定义,常量,及结构体。 版本历史 : 创建原始版本 说明 : @@ -15,8 +15,8 @@ /* CHRG application version. Keep this value synchronized with the changelog. */ #define CHRG_VERSION_MAJOR 1U #define CHRG_VERSION_MINOR 1U -#define CHRG_VERSION_PATCH 24U -#define CHRG_VERSION_STRING "V1.1.24" +#define CHRG_VERSION_PATCH 26U +#define CHRG_VERSION_STRING "V1.1.26" /* Modbus version encoding: major<<8 + minor<<4 + patch. */ #define CHRG_FIRMWARE_VERSION ((CHRG_VERSION_MAJOR << 8) + \ (CHRG_VERSION_MINOR << 4) + CHRG_VERSION_PATCH) diff --git a/apps/chrg/applications/utils/chrg_sink.c b/apps/chrg/applications/utils/chrg_sink.c index fa2cdda..9f89887 100644 --- a/apps/chrg/applications/utils/chrg_sink.c +++ b/apps/chrg/applications/utils/chrg_sink.c @@ -1,7 +1,7 @@ /**************************************************************************** 文件名称 : chrg_sink.c 完成日期 : -当前版本号 : V1.1.24 +当前版本号 : V1.1.26 主要功能 : 北向数据sink请求数据填充及发送。 版本历史 : 创建原始版本 说明 : @@ -34,6 +34,8 @@ static rt_uint8_t g_sink_init_stage[TOTAL_SOU_CHS] = {0}; static rt_tick_t g_sink_init_north_relay_tick[TOTAL_SOU_CHS] = {0}; static rt_bool_t g_sink_init_in_progress[TOTAL_SOU_CHS] = {RT_FALSE}; +/* 只有当前阶段入队成功后,才允许消费该阶段的南向批次结果。 */ +static rt_bool_t g_sink_init_stage_submitted[TOTAL_SOU_CHS] = {RT_FALSE}; /* 主继电器闭合跟踪:1=已提交ALL_REL_ON(主继电器应已闭合) 0=未闭合/已断开。 * 用于0V硬保护区分"首次开载/握手恢复"(主继电器未闭合,Now_voltage=0属正常) @@ -291,10 +293,12 @@ static int chrg_sink_init_submit_stage(eIDX_SOU_CH idx, rt_uint8_t total; rt_uint8_t start; rt_uint8_t count; + int ret; if ((idx >= TOTAL_SOU_CHS) || (pSW == RT_NULL)) { return -1; } + g_sink_init_stage_submitted[idx] = RT_FALSE; total = chrg_sink_init_build_commands(pSW, regs, vals); switch (g_sink_init_stage[idx]) { @@ -313,7 +317,11 @@ static int chrg_sink_init_submit_stage(eIDX_SOU_CH idx, default: return -1; } - return chrg_sou_com_batch_submit(idx, ®s[start], &vals[start], count); + ret = chrg_sou_com_batch_submit(idx, ®s[start], &vals[start], count); + if (ret == 0) { + g_sink_init_stage_submitted[idx] = RT_TRUE; + } + return ret; } /***************************************************************** @@ -336,20 +344,17 @@ static int chrg_sink_work_init(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) g_sink_init_in_progress[idx] = RT_TRUE; g_sink_init_stage[idx] = SINK_INIT_STAGE_CLEAR_RELAY; g_sink_init_north_relay_tick[idx] = 0U; - if (chrg_sink_init_submit_stage(idx, pSW) != 0) { - return -1; - } - + /* 入队失败也保留事务和等待入口,由等待分支持续重提当前阶段。 */ pSW->sink.On_work = 1; pSW->sink.work_mode = SINK_WORK_INIT_AUX_WAIT; - return 0; + return chrg_sink_init_submit_stage(idx, pSW); } /***************************************************************** 函数名称: chrg_sink_work_init_aux_wait 函数描述: 后台等待整组南向初始化命令完成,不阻塞北向协议握手 输入参数: idx:南向功率通道编号 pSW:当前通道控制结构体 -输出参数: 初始化完成后进入SINK_WORK_WAIT,失败时重提整个初始化批次 +输出参数: 初始化完成后进入SINK_WORK_WAIT,失败时重提当前阶段 返回说明: 0:继续等待或初始化完成 <0:参数错误 其它说明: 重试时保留当前北向sub,禁止把正在执行的协议/CC事务拉回起点 *****************************************************************/ @@ -383,6 +388,10 @@ static int chrg_sink_work_init_aux_wait(eIDX_SOU_CH idx, } return 0; } + /* 上一阶段成功后,新阶段可能因锁/队列满未入队;不能沿用旧成功结果。 */ + if (!g_sink_init_stage_submitted[idx]) { + return chrg_sink_init_submit_stage(idx, pSW); + } batch_result = chrg_sou_com_batch_get_result(idx); if (batch_result <= 0) { return chrg_sink_init_submit_stage(idx, pSW); @@ -400,6 +409,7 @@ static int chrg_sink_work_init_aux_wait(eIDX_SOU_CH idx, /* 北向可能已推进到CC或电压查询,此处只更新南向完成状态。 */ g_sink_init_in_progress[idx] = RT_FALSE; + g_sink_init_stage_submitted[idx] = RT_FALSE; pSOU->online[idx] = 1; pSW->sink.test.test_mode_old = 0; pSW->sink.work_mode = SINK_WORK_WAIT; @@ -636,6 +646,7 @@ static int chrg_sink_work_stop_prepare(eIDX_SOU_CH idx, return -1; } g_sink_init_in_progress[idx] = RT_FALSE; + g_sink_init_stage_submitted[idx] = RT_FALSE; if (chrg_sou_com_batch_cancel(idx) != 0) { return -1; } @@ -715,6 +726,13 @@ static int chrg_sink_work_switch(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) rt_uint16_t eload_value = ELOAD_LOAD; int ret; + /* Source接管后,旧Sink初始化上下文已失效;返回Sink必须重新初始化, + * 禁止把角色切换批次回码当作上次初始化阶段的完成结果。 */ + g_sink_init_in_progress[idx] = RT_FALSE; + g_sink_init_stage_submitted[idx] = RT_FALSE; + g_sink_init_stage[idx] = SINK_INIT_STAGE_CLEAR_RELAY; + g_sink_init_north_relay_tick[idx] = 0U; + ret = chrg_sou_com_batch_submit(idx, ®, &value, 1); if (ret != 0) { return ret; @@ -1007,11 +1025,7 @@ int chrg_sink_volt_judge(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) if (g_sink_init_in_progress[idx] || (pSW->sink.work_mode == SINK_WORK_INIT) || (pSW->sink.work_mode == SINK_WORK_INIT_AUX_WAIT)) { - if (g_sink_init_in_progress[idx] && - (pSW->sink.work_mode != SINK_WORK_INIT) && - (pSW->sink.work_mode != SINK_WORK_INIT_AUX_WAIT)) { - pSW->sink.work_mode = SINK_WORK_INIT_AUX_WAIT; - } + /* 统一入口负责恢复等待阶段及停止优先级,不能在这里覆盖STOP。 */ return chrg_sink_work(idx, pSW); } @@ -1191,10 +1205,12 @@ int chrg_sink_work(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) } work_mode = pSW->sink.work_mode; workmode = work_mode; - /* 初始化事务独占该通道,防止其它路径改成RUNING后追加普通启动命令。 */ + /* 已开始的初始化只能继续等待,重复INIT也不能被last_state去重卡住。 + * 停止、掉电保护和角色切换必须能抢占初始化,不能改回等待状态。 */ if (g_sink_init_in_progress[idx] && - (work_mode != SINK_WORK_INIT) && - (work_mode != SINK_WORK_INIT_AUX_WAIT)) { + (work_mode != SINK_WORK_STOP) && + (work_mode != SINK_WORK_LOW) && + (work_mode != SINK_WORK_SWITCH)) { pSW->sink.work_mode = SINK_WORK_INIT_AUX_WAIT; work_mode = SINK_WORK_INIT_AUX_WAIT; } @@ -1203,6 +1219,8 @@ int chrg_sink_work(eIDX_SOU_CH idx, struct chrg_switch_t *pSW) } /* 异步等待状态需要持续检查批次或新量测,其余相同状态保持去重。 */ if ((work_mode == last_state[idx]) && + (work_mode != SINK_WORK_INIT) && + (work_mode != SINK_WORK_STOP) && (work_mode != SINK_WORK_ON_TEST_SHORT) && (work_mode != SINK_WORK_INIT_AUX_WAIT) && (work_mode != SINK_WORK_STOP_WAIT) && diff --git a/docs/修改日志.md b/docs/修改日志.md index 8861157..a6c1cd1 100644 --- a/docs/修改日志.md +++ b/docs/修改日志.md @@ -1,5 +1,25 @@ # 修改日志 +## V1.1.26 - 2026-09-17 + +### 修复Sink角色往返时初始化上下文残留 + +- Source切回Sink时清除旧Sink初始化阶段、提交标记和延时计时;重新执行清短路及继电器等完整初始化,避免角色切换回码被误判为旧初始化成功。 +- 应用版本更新为 `V1.1.26`,对外固件版本寄存器 `0x0300` 为 `0x012A`;Bootloader不变。 + +## V1.1.25 - 2026-09-17 + +### 修复Sink重复启动及退出Short时初始化卡住 + +- 同角色Sink参数生效只复制PC可写配置,不再覆盖实时work_mode、sub、On_work、test_mode_old及量测数据。 +- 初始化/退出Short期间,相同运行请求继续原事务,不重复取消批次;不同配置返回Modbus忙(异常码0x06),PC需等待后重新发送参数生效。停止流程未完成时也拒绝新运行请求。 +- 已开始的初始化收到重复INIT时恢复到INIT_AUX_WAIT,避免被last_state去重卡住;停止和掉电保护允许抢占初始化。 +- 按阶段记录初始化命令是否入队,入队失败或批次失败时重试当前阶段,禁止用上一阶段的成功结果跳过未发送的命令。 +- 重复/拒绝的Sink配置不再无条件递增北向配置版本,避免连续参数生效导致有效回码被反复丢弃。 +- STOP入队失败后由北向轮询继续重试,不依赖On_work或通道使能。 +- 应用版本V1.1.25,对外寄存器0x0300返回0x0129;Bootloader不变。不修改Von门槛、串口超时和南向队列容量。 +- 增加主机回归测试tests/sink_init(运行真实状态机及COM生效函数,模拟南向批次和时钟)。硬件仍需验证单/四通道循环启停、反复Short进入退出、通信超时恢复及停止安全性。 + ## V1.1.24 - 2026-09-15 ### 统一应用版本定义 diff --git a/tests/sink_init/README.md b/tests/sink_init/README.md new file mode 100644 index 0000000..eaf7bcf --- /dev/null +++ b/tests/sink_init/README.md @@ -0,0 +1,28 @@ +# Sink 初始化回归验证 + +## 主机测试 + +在仓库根目录执行(需要 Node.js 和本机 GCC): + +```powershell +node tests/sink_init/run.cjs C:/Qt/Tools/mingw1310_64/bin/gcc.exe +``` + +测试编译真实 `chrg_sink.c`、从 COM 提取的配置生效函数和北向状态调度片段;只模拟时钟、南向批次、继电器及无关外设。每个场景独立进程,临时产物自动清理。 + +14 个用例覆盖:旧影子配置保留实时状态、重复退出 Short、初始化中配置变化返回忙、重复 INIT 恢复、初始化中停止、首批/后续阶段入队失败、批次失败重试、完整初始化后再次退出 Short、重复/拒绝请求不改变配置版本、停止入队失败后台重试、Sink 初始化中切换角色后重新初始化。 + +主机测试不能验证实际串口回码匹配、线程竞争、继电器时序或功率板执行结果。 + +## 硬件验收(尚未执行) + +使用现有 PC 指令和安全负载,先单通道,再四通道同时重复以下场景: + +1. 普通 5V/1A 启停、Short 进入/退出各循环至少 100 次;退出期间重复发送同一参数生效请求。预期退出初始化持续推进,最终可恢复带载。 +2. 初始化期间修改配置并生效。预期返回 Modbus 忙(异常码 `0x06`),PC 在初始化完成后重试;旧事务参数及北向回码推进不被覆盖。 +3. 在各初始化阶段发送停止;短时断开南向通信后恢复。预期停止优先、未完成阶段重试,不以旧成功结果提前进入 WAIT。 +4. 初始化未完成时切 Source,再切回 Sink。预期重新清短路、配置继电器和负载参数,不复用旧初始化阶段。 + +用 Keil Watch 观察 `sink.work_mode`、`sink.On_work`、`sink.test.test_mode_old`、`g_sink_init_in_progress`、`g_sink_init_stage`、`g_sink_init_stage_submitted`、`sub` 和 `config_version`;避免高频打印。结合南向回码及实测电流确认执行,不能仅以入队成功作为验收依据。 + +本次应用版本为 **V1.1.26**,对外固件版本寄存器 `0x0300` 为 `0x012A`;Bootloader 不变。 diff --git a/tests/sink_init/cases.c b/tests/sink_init/cases.c new file mode 100644 index 0000000..a826336 --- /dev/null +++ b/tests/sink_init/cases.c @@ -0,0 +1,210 @@ +/* 本文件由 run.cjs 拼接到真实产品函数后。每个场景独立进程,静态状态不会串扰。 */ +#include +#include +struct chrg_north_t chrgnorth; +struct chrg_south_t chrgsouth; +struct chrg_thread_t chrgthr[TOTAL_THRS]; +static rt_tick_t now; +static int pending, result, fail_submit, cancel_count, submit_count; +static int queue_capacity = 1; +static rt_uint16_t last_reg, last_val; +static rt_uint32_t sample_seq; + +#define CHECK(x) do { if (!(x)) { fprintf(stderr, "line %d: %s\n", __LINE__, #x); exit(1); } } while (0) +rt_tick_t rt_tick_get(void) { return now; } +rt_tick_t rt_tick_from_millisecond(rt_int32_t ms) { return (rt_tick_t)ms; } +int rt_kprintf(const char *format, ...) { (void)format; return 0; } +rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout) +{ (void)mutex; (void)timeout; return 0; } +rt_err_t rt_mutex_release(rt_mutex_t mutex) { (void)mutex; return 0; } +void chrg_roll_nor_source_switch_reset(rt_uint8_t ch) { (void)ch; CHECK(0); } +int chrg_source_work_state(rt_uint8_t ch, struct chrg_switch_t *p) +{ (void)ch; (void)p; CHECK(0); return -1; } +static eMBException chrg_comm_apply_source_effect(rt_uint8_t ch, + struct chrg_switch_t *p, const struct chrg_switch_t *s) +{ (void)ch; (void)p; (void)s; CHECK(0); return MB_EX_SLAVE_BUSY; } +int chrg_sou_com_batch_submit(eIDX_SOU_CH ch, const rt_uint16_t *regs, + const rt_uint16_t *vals, rt_uint8_t count) +{ + (void)ch; + CHECK(count > 0); + if (fail_submit) { fail_submit--; return -1; } + CHECK(pending < queue_capacity); /* 角色切换允许连续两批,其余场景禁止重复提交。 */ + submit_count++; + last_reg = regs[0]; last_val = vals[0]; + pending++; result = 0; + return 0; +} +int chrg_sou_com_batch_cancel(eIDX_SOU_CH ch) +{ (void)ch; cancel_count++; pending = 0; result = -1; return 0; } +rt_bool_t chrg_sou_com_batch_is_pending(eIDX_SOU_CH ch) +{ (void)ch; return pending; } +int chrg_sou_com_batch_get_result(eIDX_SOU_CH ch) +{ (void)ch; return result; } +int chrg_sou_measure_once_submit(eIDX_SOU_CH ch) { (void)ch; return 0; } +rt_uint32_t chrg_eload_get_sample_seq(rt_uint8_t ch) { (void)ch; return sample_seq; } +void chrg_nor_sw_rel(eIDX_NOR_CH ch, eIDX_ID id) { (void)ch; (void)id; } +/* 北向串口不在这些场景的执行路径中,意外调用立即失败。 */ +rt_uint8_t u16_to_u8v(rt_uint16_t value, rt_uint8_t *buf) +{ (void)value; (void)buf; CHECK(0); return 0; } +int chrg_north_pkg_encode(eIDX_ID id, rt_uint8_t cmd, rt_uint8_t *in, + rt_uint8_t len, rt_uint8_t *out, rt_uint16_t *out_len) +{ (void)id; (void)cmd; (void)in; (void)len; (void)out; (void)out_len; CHECK(0); return -1; } +int chrg_north_send(rt_uint8_t *data, rt_size_t length) +{ (void)data; (void)length; CHECK(0); return -1; } + +static struct chrg_switch_t *fixture(void) +{ + struct chrg_switch_t *p = &chrgnorth.sw[0]; + p->id = ID_SINK; + p->CV_mode = MODE_CONSTANT_CURRENT; + p->sink.On_work = 1; + p->sink.Now_State = 1; + p->sink.loadc = 1000; + p->sink.volt_on = 4000; + p->sink.pro_loadv = 5000; + p->sink.pro_loadc = 1000; + p->sink.work_mode = SINK_WORK_ON_TEST_SHORT; + p->sink.test.test_mode = SINK_TEST_SHORT; + p->sink.test.test_mode_old = SINK_TEST_SHORT; + return p; +} +static void exit_short(struct chrg_switch_t *p) +{ + struct chrg_switch_t shadow = *p; + shadow.sink.test.test_mode = 0; + CHECK(chrg_comm_apply_sink_effect(0, p, &shadow) == MB_EX_NONE); + CHECK(p->sink.work_mode == SINK_WORK_INIT_AUX_WAIT); + CHECK(pending && last_reg == 0x40 && last_val == 0); +} +static void ack(void) { pending = 0; result = 1; } +static void complete_init(struct chrg_switch_t *p) +{ + ack(); CHECK(chrg_sink_work(0, p) == 0); + CHECK(pending && last_reg == REG_REL_ON && last_val == ALL_REL_OFF); + ack(); CHECK(chrg_sink_work(0, p) == 0); + now += 31; CHECK(chrg_sink_work(0, p) == 0); + CHECK(pending && last_reg == REG_REL_ON && last_val == FU_REL_ON); + ack(); CHECK(chrg_sink_work(0, p) == 0); + CHECK(p->sink.work_mode == SINK_WORK_WAIT); + CHECK(!g_sink_init_in_progress[0] && p->sink.test.test_mode_old == 0); +} +int main(int argc, char **argv) +{ + struct chrg_switch_t *p = fixture(); + struct chrg_switch_t s; + int cancels, submits; + CHECK(argc == 2); + if (!strcmp(argv[1], "preserve_runtime")) { + p->sink.test.test_mode = p->sink.test.test_mode_old = 0; + p->sink.work_mode = SINK_WORK_RUNING; + p->sub = IDX_GET_SINK_VC; + p->Now_voltage = 5010; p->Now_current = 1000; + p->Sink_voltage_seq = 77; + CHECK(chrg_sink_work(0, p) == 0); ack(); + s = *p; + s.sink.work_mode = SINK_WORK_INIT; s.sink.On_work = 0; + s.sub = IDX_SET_SINK_CC_BEFORE_PD; + s.Now_voltage = 0; s.Now_current = 0; s.Sink_voltage_seq = 1; + CHECK(chrg_comm_apply_sink_effect(0, p, &s) == MB_EX_NONE); + CHECK(p->sink.work_mode == SINK_WORK_RUNING && p->sink.On_work == 1); + CHECK(p->sub == IDX_GET_SINK_VC && p->Now_voltage == 5010); + CHECK(p->Now_current == 1000 && p->Sink_voltage_seq == 77 && !pending); + } else if (!strcmp(argv[1], "first_submit_failure")) { + s = *p; s.sink.test.test_mode = 0; fail_submit = 1; + CHECK(chrg_comm_apply_sink_effect(0, p, &s) == MB_EX_SLAVE_BUSY); + CHECK(chrg_sink_work(0, p) == 0); + CHECK(pending && last_reg == 0x40); + complete_init(p); + } else { + exit_short(p); + cancels = cancel_count; submits = submit_count; s = *p; + if (!strcmp(argv[1], "duplicate_version") || !strcmp(argv[1], "busy_version")) { + int busy = !strcmp(argv[1], "busy_version"); + chrgnorth.config_version[0] = 17; + if (busy) s.sink.loadc = 800; + CHECK(chrg_mb_write_sys(0, SYS_REG_PARAM_EFFECT, 1, &s, + &chrgnorth, &chrgsouth) == (busy ? MB_EX_SLAVE_BUSY : MB_EX_NONE)); + CHECK(chrgnorth.config_version[0] == 17); + CHECK(g_shadow_dirty[0] == busy); + CHECK(cancel_count == cancels && pending); + } else if (!strcmp(argv[1], "duplicate_exit")) { + CHECK(chrg_comm_apply_sink_effect(0, p, &s) == MB_EX_NONE); + CHECK(cancel_count == cancels && submit_count == submits); + CHECK(p->sink.work_mode == SINK_WORK_INIT_AUX_WAIT && pending); + complete_init(p); + } else if (!strcmp(argv[1], "busy_changed_config")) { + struct chrg_switch_t before = *p; + s.sink.loadc = 800; s.sink.volt_on = 4500; + CHECK(chrg_comm_apply_sink_effect(0, p, &s) == MB_EX_SLAVE_BUSY); + CHECK(memcmp(p, &before, sizeof(*p)) == 0); + CHECK(cancel_count == cancels && submit_count == submits && pending); + } else if (!strcmp(argv[1], "recover_init")) { + p->sink.work_mode = SINK_WORK_INIT; /* 硬件截图的状态组合。 */ + CHECK(chrg_sink_work(0, p) == 0); + CHECK(p->sink.work_mode == SINK_WORK_INIT_AUX_WAIT); + CHECK(cancel_count == cancels && submit_count == submits); + complete_init(p); + } else if (!strcmp(argv[1], "stop_during_init")) { + s.sink.Now_State = 2; + CHECK(chrg_comm_apply_sink_effect(0, p, &s) == MB_EX_NONE); + CHECK(!g_sink_init_in_progress[0] && p->sink.On_work == 0); + CHECK(p->sink.work_mode == SINK_WORK_STOP_WAIT); + CHECK(pending && last_reg == REG_WORK && last_val == WORK_STOP); + } else if (!strcmp(argv[1], "stop_submit_retry")) { + s.sink.Now_State = 2; fail_submit = 1; + CHECK(chrg_comm_apply_sink_effect(0, p, &s) == MB_EX_SLAVE_BUSY); + CHECK(p->sink.work_mode == SINK_WORK_STOP && p->sink.On_work == 0); + CHECK(poll_state(p) == 0); + CHECK(p->sink.work_mode == SINK_WORK_STOP_WAIT); + CHECK(pending && last_reg == REG_WORK && last_val == WORK_STOP); + } else if (!strcmp(argv[1], "role_roundtrip")) { + ack(); CHECK(chrg_sink_work(0, p) == 0); + ack(); CHECK(chrg_sink_work(0, p) == 0); + now += 31; CHECK(chrg_sink_work(0, p) == 0); + CHECK(g_sink_init_stage[0] == SINK_INIT_STAGE_REST); + /* 模拟Source接管时取消旧批次并清On_work,保留真实Sink静态上下文。 */ + CHECK(chrg_sou_com_batch_cancel(0) == 0); + p->sink.On_work = 0; + p->source.On_Flag = 1; + p->sink.work_mode = SINK_WORK_SWITCH; + queue_capacity = 2; + CHECK(chrg_sink_work(0, p) == 0); + CHECK(p->sink.work_mode == SINK_WORK_INIT); + ack(); /* 角色切换批次已成功,但不代表新Sink初始化成功。 */ + CHECK(chrg_sink_work(0, p) == 0); + CHECK(p->sink.work_mode == SINK_WORK_INIT_AUX_WAIT); + CHECK(p->sink.On_work == 1 && pending && last_reg == 0x40); + complete_init(p); + } else if (!strcmp(argv[1], "rest_submit_failure")) { + ack(); CHECK(chrg_sink_work(0, p) == 0); + ack(); CHECK(chrg_sink_work(0, p) == 0); + now += 31; fail_submit = 1; + CHECK(chrg_sink_work(0, p) != 0); + CHECK(!pending && result == 1); + CHECK(chrg_sink_work(0, p) == 0); + CHECK(pending && last_reg == REG_REL_ON && last_val == FU_REL_ON); + CHECK(g_sink_init_in_progress[0] && p->sink.test.test_mode_old == 1); + ack(); CHECK(chrg_sink_work(0, p) == 0); + CHECK(p->sink.work_mode == SINK_WORK_WAIT); + } else if (!strcmp(argv[1], "stage_submit_failure")) { + ack(); fail_submit = 1; + CHECK(chrg_sink_work(0, p) != 0); + CHECK(!pending && result == 1); /* 故意保留上一阶段成功结果。 */ + CHECK(chrg_sink_work(0, p) == 0); + CHECK(pending && last_reg == REG_REL_ON && last_val == ALL_REL_OFF); + CHECK(g_sink_init_stage[0] == SINK_INIT_STAGE_AUX_RELAY); + } else if (!strcmp(argv[1], "failed_batch_retry")) { + pending = 0; result = -1; + CHECK(chrg_sink_work(0, p) == 0); + CHECK(pending && last_reg == 0x40 && p->sink.test.test_mode_old == 1); + complete_init(p); + } else if (!strcmp(argv[1], "complete_then_restart")) { + complete_init(p); s = *p; s.sink.test.test_mode = 1; + CHECK(chrg_comm_apply_sink_effect(0, p, &s) == MB_EX_NONE); + CHECK(p->sink.work_mode == SINK_WORK_ON_TEST_SHORT && pending); + ack(); exit_short(p); complete_init(p); + } else { CHECK(0); } + } + return 0; +} diff --git a/tests/sink_init/rtdevice.h b/tests/sink_init/rtdevice.h new file mode 100644 index 0000000..b5a6ae7 --- /dev/null +++ b/tests/sink_init/rtdevice.h @@ -0,0 +1 @@ +#include "rtthread.h" diff --git a/tests/sink_init/rthw.h b/tests/sink_init/rthw.h new file mode 100644 index 0000000..b5a6ae7 --- /dev/null +++ b/tests/sink_init/rthw.h @@ -0,0 +1 @@ +#include "rtthread.h" diff --git a/tests/sink_init/rtthread.h b/tests/sink_init/rtthread.h new file mode 100644 index 0000000..eac894b --- /dev/null +++ b/tests/sink_init/rtthread.h @@ -0,0 +1,37 @@ +#ifndef HOST_RTTHREAD_H +#define HOST_RTTHREAD_H +#include +#include +#include +typedef uint8_t rt_uint8_t; +typedef int8_t rt_int8_t; +typedef uint16_t rt_uint16_t; +typedef int16_t rt_int16_t; +typedef uint32_t rt_uint32_t; +typedef int32_t rt_int32_t; +typedef uintptr_t rt_ubase_t; +typedef intptr_t rt_base_t; +typedef size_t rt_size_t; +typedef int rt_bool_t; +typedef int rt_err_t; +typedef uint32_t rt_tick_t; +typedef void *rt_device_t; +typedef void *rt_mutex_t; +typedef void *rt_event_t; +typedef void *rt_sem_t; +typedef void *rt_mailbox_t; +typedef void *rt_mq_t; +#define RT_NULL NULL +#define RT_TRUE 1 +#define RT_FALSE 0 +#define RT_WAITING_FOREVER (-1) +#define RT_TICK_PER_SECOND 1000 +#define rt_memcpy memcpy +#define rt_memcmp memcmp +#define rt_memset memset +rt_tick_t rt_tick_get(void); +rt_tick_t rt_tick_from_millisecond(rt_int32_t ms); +int rt_kprintf(const char *format, ...); +rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout); +rt_err_t rt_mutex_release(rt_mutex_t mutex); +#endif diff --git a/tests/sink_init/run.cjs b/tests/sink_init/run.cjs new file mode 100644 index 0000000..542c1a7 --- /dev/null +++ b/tests/sink_init/run.cjs @@ -0,0 +1,62 @@ +// 使用真实 Sink 实现及 COM 生效函数;仅替换时钟、继电器和南向传输。 +// node tests/sink_init/run.cjs [gcc路径]。临时编译产物不写入固件工程。 +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const cp = require('node:child_process'); +const root = path.resolve(__dirname, '../..'); +const app = path.join(root, 'apps/chrg/applications'); +const cc = process.argv[2] || process.env.CC || 'gcc'; +if (path.isAbsolute(cc)) process.env.PATH = path.dirname(cc) + path.delimiter + process.env.PATH; +const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'chrg-sink-test-')); +const source = fs.readFileSync(path.join(app, 'thread/chrg_comm.c'), 'utf8'); +function definition(name) { + const pattern = new RegExp('static\\s+[\\w]+\\s+' + name + '\\s*\\([^;{]*\\)\\s*\\{', 'g'); + const match = pattern.exec(source); + if (!match) return ''; + let depth = 1, end = pattern.lastIndex; + while (depth && end < source.length) { + if (source[end] === '{') depth++; + if (source[end] === '}') depth--; + end++; + } + return source.slice(match.index, end); +} +const type = source.match(/typedef struct\s*\{[^}]*\}\s*chrg_param_diff_t;/)[0]; +const functions = ['chrg_comm_get_param_diff', 'chrg_comm_sink_config_equal', + 'chrg_comm_copy_sink_config', 'chrg_comm_handle_sink_load_current', + 'chrg_sink_diff_send_reg', 'chrg_comm_apply_sink_effect', 'chrg_mb_write_sys']; +const roll = fs.readFileSync(path.join(app, 'thread/chrg_roll_nor.c'), 'utf8'); +const dispatch = roll.slice(roll.indexOf('void chrg_roll_nor_thread_entry')) + .match(/if \(\(pSW->id == ID_SOURCE\)[\s\S]+?state_work_ret = chrg_sink_work[\s\S]+?\n\t\t\t\}/)[0]; +const unit = '#include "' + path.join(app, 'utils/chrg_sink.c').replaceAll('\\', '/') + '"\n' + + '#include "chrg_thread.h"\n#define LOG_E(...) ((void)0)\n' + + 'static rt_uint8_t g_shadow_dirty[4], last_pd_pro[4];\n#define SOURCE_PD_STATE_UNKNOWN 255\n' + + 'static eMBException chrg_comm_apply_source_effect(rt_uint8_t ch, struct chrg_switch_t *p, const struct chrg_switch_t *s);\n' + + type + '\n' + functions.map(definition).join('\n') + '\n' + + 'static int poll_state(struct chrg_switch_t *pSW) { int idx=0, state_work_ret=0;\n' + dispatch + '\nreturn state_work_ret; }\n' + + fs.readFileSync(path.join(__dirname, 'cases.c'), 'utf8'); +const scenarios = ['duplicate_exit', 'preserve_runtime', 'busy_changed_config', + 'recover_init', 'stop_during_init', 'first_submit_failure', 'stage_submit_failure', + 'failed_batch_retry', 'complete_then_restart', 'duplicate_version', + 'busy_version', 'stop_submit_retry', 'rest_submit_failure', 'role_roundtrip']; +try { + fs.writeFileSync(path.join(dir, 'test.c'), unit); + const exe = path.join(dir, process.platform === 'win32' ? 'test.exe' : 'test'); + const args = ['-std=c99', '-O0', '-ffunction-sections', '-fdata-sections', + '-Werror=implicit-function-declaration', '-Wl,--gc-sections', + '-I' + __dirname, ...['utils', 'thread', 'bsp'].map(p => '-I' + path.join(app, p)), + path.join(dir, 'test.c'), '-o', exe]; + const build = cp.spawnSync(cc, args, {encoding: 'utf8'}); + if (build.status !== 0) throw Error(build.stderr || String(build.error || build.status)); + let failed = 0; + for (const name of scenarios) { + const run = cp.spawnSync(exe, [name], {encoding: 'utf8'}); + console.log((run.status === 0 ? 'PASS ' : 'FAIL ') + name); + if (run.status !== 0) { failed++; console.log(run.stdout + run.stderr); } + } + console.log(`${scenarios.length - failed}/${scenarios.length} passed`); + process.exitCode = failed ? 1 : 0; +} finally { + fs.rmSync(dir, {recursive: true, force: true}); +}