dry_node.c
Handle_Dry_Node_Logic() 的分支优先级(dry_node.c:1767)4.2 新版流水线 Dry_Node_Logic_ii()(dry_node.c:1726)4.3 采集 update_dry_node_data()(dry_node.c:1117)4.4 模式选择 check_dry_node_mode()(dry_node.c:1185)4.5 星期与时间段判定4.6 期望动作 deal_dry_node_mode()(dry_node.c:1403)4.7 节流状态机 deal_dry_node_tick()(dry_node.c:1536)4.8 输出驱动 driver_dry_node()(dry_node.c:1664)4.9 旧版就地逻辑 Dry_Node1_Logic() / Dry_Node2_Logic()4.10 外置 NPE handle_outside_npe_function()(dry_node.c:1693)5. 对外接口本文件导出本文件调用的外部函数 / 变量6. 依赖关系依赖被依赖7. 备注与风险点源文件路径:
midware/interface/dry_node/dry_node.c所属模块:midware / interface / dry_node(干接点输出接口) 代码行数:1911
1. 文件职责
本文件实现 EMS 主控板两路干接点(继电器)输出的全部现场逻辑,是"储能状态 → 外部负载/热泵/柴油机/EPO 联动"的翻译层。它把电池 SOC、电网电表功率、逆变器运行模式(含 UPS)、当前星期与时间段、云端/Modbus 下发的配置参数合并判断,最终驱动 DRY_NODE1_ON/OFF、DRY_NODE2_ON/OFF 两个 GPIO。文件同时承载两代逻辑:旧版就地判定 Dry_Node1_Logic() / Dry_Node2_Logic()(SOC + 馈网功率 + 延迟/持续/暂停节流),以及新版 Dry_Node_Logic_ii()(13 种 DRY_NODE_II_MODE_E 模式的流水线式处理)。对外唯一入口是 Handle_Dry_Node_Logic(),由 midware/interface/interface.c:221 的 task_interface_parse 任务每周期调用一次(该任务 drm_mode_judge() 之后、query_parameter_save_tags() 之前),因此本文件没有自己的任务或信号量,是纯轮询式的被动执行体。除常规干接点外,它还顺手承担三件"挂靠"工作:柴油机干接点跟随、老化模式干接点跟随、外置 NPE(N-PE 短接继电器)控制与 EPO 干接点输入采集。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
STDryNodeTab / STDryNodeInfo | 结构体(#pragma pack(1),定义于 dry_node.h:133) | 干接点持久化配置表,本文定义实例(dry_node.c:29)。对应 Flash DRY_NODE_PARA_PAGE(4078) 一个扇区镜像,含 u16_head、u16_crc_code、u8_reserve[28];字段分 1/2 通道各一份:u8ChannelN、u8ControlModeN、u8StartTimeNA/NB、u8EndTimeNA/NB、u8DateN、u8ChargeSOCN、u8ChargeModeN、u8UPSN、u32SwitchOnN、u32SwitchOffN、u8DelayN、u8DurationN、u8PauseN、u8StatusN、u32DelayTickN、u32DurationTickN、u32PauseTickN、u8RelayDoneN,另有新版的 u8FeedModeN、u16FeedValueN、u8SGEnableN、u16StartTimeMNA/MNB、u16EndTimeMNA/MNB、u8_modbus_dry1/2 |
DRY_NODE_II_TAB_T | 结构体(dry_node.h:93) | 新版 II 逻辑的运行期工作区。由配置字段(u8_control_mode、u8_ups_enable、u8_sg_ready_enable、u16_timeX_start/end_tick、u8_date、u8_set_soc_mode_value、u8_set_feed_mode、u32_set_feed_value、u8_delay/duration/pause_time)+ 运行状态(u8_pre/cur_dry_node_mode、u8_dry_node_mode_status、u8_dry_node_ctrl、u8_time_flag、u8_week_flag、三组 *_tick)+ 现场量(u16_sys_workmode、u8_bat_soc、s32_meter_actpower_t)构成。本文定义实例 g_t_dry_node_ii_info1/2(dry_node.c:30) |
DRY_NODE_II_MODE_E | 枚举(dry_node.h:65) | II 模式全集 13 项:0=DRY_NODE_NONE_MODE、1=DRY_NODE_UPS_MODE、2=DRY_NODE_SGREADY_MODE、3=TIME_MODE_SWITCH_ON、4=TIME_MODE_SWITCH_OFF、5/6=AUTO_MODE_SOC_ON_UPS_ON_AT_LEAST/AT_MOST、7/8=AUTO_MODE_SOC_ON_FEED_ON_AT_LEAST/AT_MOST、9/10=AUTO_MODE_SOC_ON_FEED_OFF_AT_LEAST/AT_MOST、11/12=AUTO_MODE_SOC_OFF_FEED_ON_AT_LEAST/AT_MOST。DRY_NODE_UPS_MODE、DRY_NODE_SGREADY_MODE 在本文件中从未被赋值,属预留值 |
DRY_NODE_MODE_STATUS_E | 枚举(dry_node.h:82) | 节流状态机状态:DRY_NODE_STATUS_NONE(0)、DRY_NODE_DELAY_STATUS(1)、DRY_NODE_DURATION_STATUS(2)、DRY_NODE_PAUSE_STATUS(3)、DRY_NODE_STATUS_MAX(4) |
ENDryNodeDo | 枚举(dry_node.h:32) | 继电器动作请求:NODE_DONE_OPEN=0x01、NODE_DONE_CLOSE=0x02、NODE_DONE_NONE=0x03(可操作)。写入 u8RelayDoneN / u8_dry_node_ctrl |
ENDryNodeStatus | 枚举(dry_node.h:39) | 继电器实际回读状态:NODE_OPEN=0x00、NODE_CLOSE=0x01(由 DI_x_DRY_CONTACT 得到) |
ENDryNodeMode | 枚举(dry_node.h:45) | 控制模式:MODE_ON=1、MODE_OFF=2、MODE_AUTO=3 |
ENDryNodeCharge | 枚举(dry_node.h:52) | SOC/馈网判据:AT_LEAST=1、AT_MOST=2、AT_DISABLE=3 |
ENDryNodeNum | 枚举(dry_node.h:59) | 通道号:DRY_NODE_ONE=1、DRY_NODE_TWO=2 |
DRY_NODE1_ON / DRY_NODE1_OFF | 宏(dry_node.h:20-21) | 等价 do0_output_on() / do0_output_off()(hardware/gpio/gpio.c) |
DRY_NODE2_ON / DRY_NODE2_OFF | 宏(dry_node.h:26-27) | 等价 do1_output_on() / do1_output_off() |
DI_1_DRY_CONTACT / DI_2_DRY_CONTACT | 宏(dry_node.h:23,29) | 继电器回读:do0_output_bit_get() / do1_output_bit_get() |
DI_1_ACCESS_POINT / DI_2_ACCESS_POINT | 宏(dry_node.h:24,30) | 数字输入:di0_input_bit_get() / di1_input_bit_get()(EPO 信号源) |
DEBUG_DRY_NODE | 宏(dry_node.h:18,值 0) | 打开后每次判定会输出数十条 printf 定位语句,量产必须为 0 |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
Get_Dry_Node_Status | 全局(无 static,dry_node.c:43) | 按通道号返回对应继电器触点回读值(DI_x_DRY_CONTACT) |
Get_Week_By_Date | 全局(dry_node.c:54) | 用类 Zeller 公式把 SYSTEM_DATETIME 换算成 1~7 的星期序号(1=周一) |
Judge_By_Time | 全局(dry_node.c:96) | 按小时判断当前时间是否落在 [start, end) 区间,支持跨天;起止相同视为无效 |
Dry_Node1_Logic | 全局(dry_node.c:144) | 旧版通道 1 就地逻辑:UPS 优先,其次 ON/OFF/AUTO 三模式,AUTO 下用 SOC+馈网功率+延迟/持续/暂停节流 |
Dry_Node2_Logic | 全局(dry_node.c:630) | 旧版通道 2 就地逻辑,与通道 1 结构完全同构(字段换 *2、宏换 DRY_NODE2_*) |
update_dry_node_data | 全局(dry_node.c:1117) | 按 channel 把 STDryNodeInfo 配置 + 现场量(运行模式/SOC/电表功率)刷入 g_t_dry_node_ii_info1/2 |
check_dry_node_mode | 全局(dry_node.c:1185) | 仅按配置项(UPS 使能/SG-Ready 使能/SOC 模式/馈网模式)挑选 u8_cur_dry_node_mode,不判断条件是否成立 |
judge_by_week_time | 全局(dry_node.c:1289) | 按分钟判断两段时间是否命中,并判断当天是否在 u8_date 星期掩码内,写 u8_time_flag / u8_week_flag |
deal_dry_node_mode | 全局(dry_node.c:1403) | 按 u8_cur_dry_node_mode 的 11 个有效分支求 u8_dry_node_ctrl(期望的继电器动作) |
deal_dry_node_tick | 全局(dry_node.c:1536) | 延时/持续/暂停三态节流状态机,并对 OSTimeGet() 回绕做了补偿判断 |
driver_dry_node | 全局(dry_node.c:1664) | 把 u8_dry_node_ctrl 落到具体通道的 DRY_NODE1_ON/OFF 或 DRY_NODE2_ON/OFF |
handle_outside_npe_function | 全局(dry_node.c:1693) | 外置 NPE 场景下用通道 1 干接点表示 UPS 态:市电三相机电压最大值 > 60 V 或退出 UPS 则断开,进入 UPS 则闭合 |
Dry_Node_Logic_ii | 全局(dry_node.c:1726) | 新版 II 逻辑的 6 段流水线:更新数据 → 选模式 → 判周/时间 → 求动作 → 节流 → 驱动 |
Handle_Dry_Node_Logic | 全局(dry_node.c:1767) | 对外唯一入口:先做柴油机/老化/外置 NPE 的恢复处理与 EPO 干接点采集,再按优先级选择 II 逻辑或旧版双通道逻辑 |
4. 核心逻辑解析
4.1 入口 Handle_Dry_Node_Logic() 的分支优先级(dry_node.c:1767)
函数用一组互斥的 if / else if 决定本周期"谁能驱动 DRY_NODE1",顺序即优先级:
- 状态跳变复位(
dry_node.c:1782、1792、1801):用三个static变量分别镜像t_config_data.Generator、DispatchInfo.u8ControlMode、t_config_data.u8_outside_npe_en的上一次值。一旦发现由开变关,就地恢复默认:柴油机模式退出 →u8_generator_on = FALSE且DRY_NODE1_OFF;退出AGING_MODE→DRY_NODE1_OFF;外置 NPE 关闭 →inverter_set_n_n_short(1)、inverter_set_n_pe_short_enable(TRUE)、DRY_NODE1_OFF;外置 NPE 打开 →inverter_set_n_n_short(2)、inverter_set_n_pe_short_enable(FALSE)。 - 上电 10 秒延时 + EPO 干接点采集(
dry_node.c:1777、1817):首次调用时把s_u32_epo_di_delay_start = OSTimeGet();之后仅在OSTimeGet() - s_u32_epo_di_delay_start > 10*1000时才调用epo_set_signal_status(采样值)。采样源按机型分流:INVERTER_TYPE == ALPHAESS_TYPE且子型号为SUB_S5/SUB_B5/SUB_S3_6时,强制把Generator置 FALSE 并取DI_1_ACCESS_POINT,否则取DI_2_ACCESS_POINT。这一分支只在epo_read_config_enable() == TRUE时生效。 - 外置 NPE(
dry_node.c:1839):u8_outside_npe_en == TRUE→ 只跑handle_outside_npe_function(),本周期不再处理普通干接点。 - 柴油机(
dry_node.c:1843):Generator == TRUE→DRY_NODE1跟随g_t_logic_info.u8_generator_on(由logic/generator.c设定)。G3 单相机(SUB_S5/SUB_B5/SUB_S3_6)在 EPO 使能时被显式跳过。 - 老化模式(
dry_node.c:1865):DispatchInfo.u8ControlMode == AGING_MODE→u16_mppt_percentage == 0闭合,否则断开。 - OEM_HHE 定制(
dry_node.c:1876):g_t_system_base_info.u16_oem_flag == OEM_HHE时,无 EMS 故障 + BMS 无故障 + DI_1 未接 → 断开,其余情况闭合(安全反逻辑:有故障就闭合)。 - 默认干接点业务(
dry_node.c:1887):依旧先排除 G3 单相机 + EPO 使能的情况;随后按配置分流——STDryNodeInfo.u8FeedMode1 != 0 || u8FeedMode2 != 0→ 走新版Dry_Node_Logic_ii();- 否则 → 走旧版
Dry_Node1_Logic(); Dry_Node2_Logic();。
即:"馈网模式配置项是否为 0"是新旧两代逻辑的切换开关。
4.2 新版流水线 Dry_Node_Logic_ii()(dry_node.c:1726)
固定 6 步、每步对通道 1 和 2 各调一次,无内部状态、无分支:
update_dry_node_data(CH1/CH2) // 采集
check_dry_node_mode(CH1/CH2) // 选模式
judge_by_week_time(CH1/CH2) // 周/时间
deal_dry_node_mode(CH1/CH2) // 期望动作
deal_dry_node_tick(CH1/CH2) // 节流
driver_dry_node(CH1/CH2) // 输出
4.3 采集 update_dry_node_data()(dry_node.c:1117)
虽然是"按指针入参",实际函数体完全忽略 t_dry_node_info,直接写全局 g_t_dry_node_ii_info1 或 g_t_dry_node_ii_info2(switch(channel) 只有 DRY_NODE_ONE/DRY_NODE_TWO 两例,无 default)。每次调用都现场取三个量,因此 II 逻辑的时效性等于调用周期:
u16_sys_workmode = inverter_read_run_mode()u8_bat_soc = bms_read_total_soc() / 10(0.1% → 1%)s32_meter_actpower_t = meter_read_actpower(GRID_METER, PHASE_T)(s32,保留符号,负值代表馈网)
4.4 模式选择 check_dry_node_mode()(dry_node.c:1185)
u8_dry_node_enable == FALSE → DRY_NODE_NONE_MODE
control_mode == MODE_ON → TIME_MODE_SWITCH_ON
control_mode == MODE_OFF → TIME_MODE_SWITCH_OFF
control_mode == MODE_AUTO:
ups_enable == TRUE && sys_workmode == cUPS:
set_soc_mode == AT_LEAST → AUTO_MODE_SOC_ON_UPS_ON_AT_LEAST
set_soc_mode == AT_MOST → AUTO_MODE_SOC_ON_UPS_ON_AT_MOST
else:
sg_ready_enable == TRUE:
SOC=AT_LEAST & FEED=AT_LEAST → AUTO_MODE_SOC_ON_FEED_ON_AT_LEAST
SOC=AT_LEAST & FEED=AT_DISABLE → AUTO_MODE_SOC_ON_FEED_OFF_AT_LEAST
SOC=AT_DISABLE & FEED=AT_LEAST → AUTO_MODE_SOC_OFF_FEED_ON_AT_LEAST
sg_ready_enable == FALSE:
AT_LEAST/AT_LEAST → AUTO_MODE_SOC_ON_FEED_ON_AT_LEAST
AT_MOST /AT_MOST → AUTO_MODE_SOC_ON_FEED_ON_AT_MOST
AT_LEAST/AT_DISABLE → AUTO_MODE_SOC_ON_FEED_OFF_AT_LEAST
AT_MOST /AT_DISABLE → AUTO_MODE_SOC_ON_FEED_OFF_AT_MOST
AT_DISABLE/AT_LEAST → AUTO_MODE_SOC_OFF_FEED_ON_AT_LEAST
AT_DISABLE/AT_MOST → AUTO_MODE_SOC_OFF_FEED_ON_AT_MOST
注意:SG-Ready 使能时不支持 AT_MOST(三条分支全是 AT_LEAST/AT_DISABLE 组合),且所有未命中的组合(含 AT_DISABLE/AT_DISABLE)都不做赋值,u8_cur_dry_node_mode 会保留上一次的值。
4.5 星期与时间段判定
Get_Week_By_Date()(dry_node.c:54) 用 Zeller 同余的变形公式:
if (month == 1 || month == 2) { Year = Years - 1; Month = Month + 12; }
Week = (Century/4) - 2*Century + Year + Year/4 + 13*(Month+1)/5 + Day - 1;
Week %= 7; if (Week == 0) Week = 7; // 结果域 1..7,1=周一
Century 是常量 20(写死),因此只对 20xx 年成立。
Judge_By_Time(start, end)(dry_node.c:96) 只比 SYSTEM_DATETIME.bHours(小时粒度):start == end → FALSE;start < end → start <= h < end;start > end(跨天)→ h >= start || h < end。
judge_by_week_time()(dry_node.c:1289) 是 II 逻辑的对应实现,改用 SYSTEM_DATETIME.bHours*60 + bMinutes 的分钟粒度 l_system_tick,对两段时间各得出 l_u8_timeN_flag(0=段内有效、1=段外、2=段无效),再合并:任一段 flag==0 → u8_time_flag = 0;否则任一段 flag==1 → = 1;否则 → = 2。跨天段(start >= end)在本函数中被判为无效(越界即"时间段内有效"的反向写法,dry_node.c:1326–1336)——与 Judge_By_Time 的跨天语义相反。星期判定同旧版:u8_date & (1 << (week-1)),命中写 u8_week_flag = 0,否则 1(dry_node.c:1385)。
4.6 期望动作 deal_dry_node_mode()(dry_node.c:1403)
以 u8_cur_dry_node_mode 为 switch 键,输出 u8_dry_node_ctrl(NODE_DONE_CLOSE / NODE_DONE_OPEN);default 一律 OPEN。
| 模式 | 闭合(CLOSE)条件 |
|---|---|
TIME_MODE_SWITCH_ON | u8_time_flag == 0 && u8_week_flag == 0 |
TIME_MODE_SWITCH_OFF | 上述条件不成立(时间/星期反逻辑) |
AUTO_MODE_SOC_ON_UPS_ON_AT_LEAST | bat_soc >= set_soc_value |
AUTO_MODE_SOC_ON_UPS_ON_AT_MOST | bat_soc <= set_soc_value |
AUTO_MODE_SOC_ON_FEED_ON_AT_LEAST | bat_soc >= set_soc_value 且 meter_actpower_t < 0 && abs(...) >= set_feed_value |
AUTO_MODE_SOC_ON_FEED_ON_AT_MOST | bat_soc <= set_soc_value 且(power < 0 && abs(power) <= set_feed_value 或 power > 100) |
AUTO_MODE_SOC_ON_FEED_OFF_AT_LEAST | bat_soc >= set_soc_value |
AUTO_MODE_SOC_ON_FEED_OFF_AT_MOST | bat_soc <= set_soc_value |
AUTO_MODE_SOC_OFF_FEED_ON_AT_LEAST | power < 0 && abs(power) >= set_feed_value |
AUTO_MODE_SOC_OFF_FEED_ON_AT_MOST | (power < 0 && abs(power) <= set_feed_value)或 power > 100 |
power > 100 中的 100 是写死的魔法数(正向取电超过 100 W 即视为无馈网)。
4.7 节流状态机 deal_dry_node_tick()(dry_node.c:1536)
状态迁移(u8_dry_node_mode_status):
- 入口先比较
u8_cur_dry_node_mode != u8_pre_dry_node_mode:不同则同步 pre、强制回到DRY_NODE_DELAY_STATUS,并把u32_delay_tick、u32_duration_tick、u32_pause_tick三者一起刷新为OSTimeGet()。 - 时间模式(
TIME_MODE_SWITCH_ON/OFF)直接break,不受该状态机影响——时间模式没有延时/持续/暂停保护。 - AUTO 系列模式按三态推进:
DELAY: ctrl == CLOSE 且 (now - delay_tick) > delay_time*60*1000
→ DURATION_STATUS, duration_tick = now
ctrl == CLOSE 且未超时 → 强制 ctrl = OPEN(延时未到,压住闭合)
ctrl != CLOSE + → delay_tick = now(刷新延时)
DURATION:ctrl == OPEN 且超 duration_time → PAUSE_STATUS, pause_tick = now
ctrl == OPEN 且未超时 → 强制 ctrl = CLOSE(持续期内禁止断开)
ctrl != OPEN → pause_tick = now
PAUSE: ctrl == CLOSE 且超 pause_time → DELAY_STATUS, delay_tick = now
ctrl == CLOSE 且未超时 → 强制 ctrl = OPEN(暂停期内禁止闭合)
ctrl != CLOSE → delay_tick = now
default: ctrl = NODE_DONE_OPEN
即语义为:"延时"保证动作前先等够 delay_time,"持续"保证闭合后至少保持 duration_time,"暂停"保证断开后至少静默 pause_time。三处超时判断都写了两个条件,第二个专门处理 OSTimeGet() 的 32 位回绕:(now > tick && now - tick > T) || (tick > now && (0xFFFFFFFF - tick + now) > T)(dry_node.c:1567、1597、1626 等)。0xFFFFFFFF - tick + now 少加了 1,回绕时判定偏保守 1 ms。
4.8 输出驱动 driver_dry_node()(dry_node.c:1664)
DRY_NODE_ONE → u8_dry_node_ctrl == NODE_DONE_CLOSE ? DRY_NODE1_ON : DRY_NODE1_OFF;DRY_NODE_TWO 同理走 DRY_NODE2_*。无 default 分支。注意此处用 else 兜底所有非 CLOSE 值:u8_dry_node_ctrl 若还是 NODE_DONE_NONE(未初始化过)也会落到 OFF,不会出现"三态无输出"。
4.9 旧版就地逻辑 Dry_Node1_Logic() / Dry_Node2_Logic()
两者结构完全同构(通道 1 见 dry_node.c:144–621,通道 2 见 dry_node.c:630–1107),下面以通道 1 描述,通道 2 只是字段后缀换成 *2、驱动宏换成 DRY_NODE2_*。
输入:u8BatSoc = bms_read_total_soc()/10、s16ActPower = meter_read_actpower(GRID_METER, PHASE_T)(本地截断为 s16)、l_u8Ch1TimeSectorA/B = Judge_By_Time(...)、l_u8Week = Get_Week_By_Date();回读触点写回 STDryNodeInfo.u8Status1。
主分支结构:
u8Channel1 != ENABLE:
if (s_u8_enable_flag1 == TRUE) { u8RelayDone1 = NODE_DONE_OPEN; flag = FALSE; } // 从使能变禁用,恢复默认
else u8RelayDone1 = NODE_DONE_NONE;
u32DelayTick1 = OSTimeGet();
u8Channel1 == ENABLE:
s_u8_enable_flag1 = TRUE;
if (inverter_read_run_mode() == cUPS): // UPS 模式最高优先
s_u8_ups_flag1 = TRUE;
if (u8UPS1 == ENABLE)
u8RelayDone1 = (BatSoc <= u8ChargeSOC1) ? NODE_DONE_CLOSE : NODE_DONE_OPEN;
else
u8RelayDone1 = NODE_DONE_NONE;
else:
MODE_ON: u8Date1 & (1<<(week-1)) ? (A||B 命中 ? CLOSE : OPEN) : OPEN
MODE_OFF: u8Date1 & (1<<(week-1)) ? (A||B 命中 ? OPEN : CLOSE) : CLOSE
MODE_AUTO: 见下
else: u8RelayDone1 = NODE_DONE_NONE;
若本周期从 UPS 跳出(s_u8_ups_flag1 == TRUE)→ u8RelayDone1 = NODE_DONE_OPEN; flag = FALSE;
AUTO 模式(dry_node.c:300–495)把"SOC 判据"与"馈网功率判据"组合成两族,且对常开/常闭两种接线做了反向映射:
- 族 A(常开端,
u8ChargeMode1 == AT_LEAST && BatSoc >= u8ChargeSOC1,或== AT_DISABLE):- 当前断开(
u8Status1 == NODE_OPEN,负载切出):若ActPower < 0 && abs(ActPower) >= u32SwitchOn1(有馈网且超闭合阈值),且u8RelayDone1 == NODE_DONE_NONE且距u32DelayTick1已过u8Delay1*60*1000→ 记录u32DurationTick1 = OSTimeGet(),置NODE_DONE_CLOSE;条件不满足 → 刷新u32DelayTick1。 - 当前闭合(负载切入):若
ActPower >= 100或(ActPower < 0 && abs(ActPower) <= u32SwitchOff1)→ 同样需过延时,置NODE_DONE_OPEN并记录u32PauseTick1;否则刷新u32DelayTick1。
- 当前断开(
- 族 B(常闭端,
u8ChargeMode1 == AT_MOST && BatSoc <= u8ChargeSOC1):判据与置位整体互换——欠 SOC 时反而在"有馈网"时置NODE_DONE_OPEN并记u32PauseTick1,无馈网时置NODE_DONE_CLOSE并记u32DurationTick1。这与注释"负载接在常闭端"一致,但代码读起来与族 A 相反,容易误改。
动作执行段(dry_node.c:543–620):u8RelayDone1 == NODE_DONE_OPEN → DRY_NODE1_OFF,AUTO 模式下等过 u8Pause1*60*1000(自 u32PauseTick1)才把标记复位为 NODE_DONE_NONE 并刷新 u32DelayTick1;非 AUTO 模式立即复位。u8RelayDone1 == NODE_DONE_CLOSE → DRY_NODE1_ON,AUTO 模式下等过 u8Duration1*60*1000 复位,非 AUTO 立即复位。注意旧版逻辑在判定函数内部直接操作继电器 GPIO,不走 driver_dry_node()。
4.10 外置 NPE handle_outside_npe_function()(dry_node.c:1693)
取三相 inverter_read_grid_volt(PHASE_L1/L2/L3) 的最大值(两次三元比较)。断开条件:max(grid_volt) > 60(市电已恢复)或(当前不在 cUPS 且上一周期在 UPS)。闭合条件:inverter_read_run_mode() == cUPS。用 static u8 s_u8_ups_flag 记忆 UPS 跳变。60 V 是判断"市电是否存在"的经验阈值。
5. 对外接口
本文件导出
| 名称 | 类型 | 使用方 |
|---|---|---|
STDryNodeInfo | 全局变量(dry_node.c:29) | data/system_config/system_config.c(Flash 读写与 CRC)、midware/interface/modbus/modbus.c(寄存器读写,约 5068–5201 读、7876–8009 写)、net/ems.c(上报/接收)、net/server.c(JSON 字段表 3969–4006、4834–4849) |
g_t_dry_node_ii_info1 / g_t_dry_node_ii_info2 | 全局变量(dry_node.c:30) | 仅本文件内部使用(II 逻辑工作区),无外部引用 |
Handle_Dry_Node_Logic | 函数(声明于 dry_node.h:209) | midware/interface/interface.c:221(task_interface_parse 主循环) |
Get_Dry_Node_Status、Get_Week_By_Date、Judge_By_Time、Dry_Node1_Logic、Dry_Node2_Logic、update_dry_node_data、check_dry_node_mode、judge_by_week_time、deal_dry_node_mode、deal_dry_node_tick、driver_dry_node、handle_outside_npe_function、Dry_Node_Logic_ii | 函数 | 均为外部链接但未在头文件声明,实际只被本文件内 Handle_Dry_Node_Logic / Dry_Node_Logic_ii 调用;dry_node.h:208 声明的 dry_node_io_init() 在本文件没有实现(死声明) |
本文件调用的外部函数 / 变量
| 来源 | 名称 |
|---|---|
hardware/gpio/gpio.h | do0_output_on/off、do0_output_bit_get、di0_input_bit_get、do1_output_on/off、do1_output_bit_get、di1_input_bit_get |
midware/bms/bms.h:1190 | bms_read_total_soc()(返回 0.1% 单位) |
midware/meter/meter.h:471 | meter_read_actpower(site, phase)(s32,负值=馈网) |
midware/inverter/inverter.h | inverter_read_run_mode()(2011)、inverter_read_grid_volt()(1986)、inverter_set_n_n_short()(2100)、inverter_set_n_pe_short_enable()(2102) |
logic/logic.h | epo_read_config_enable()(691)、epo_set_signal_status()(694)、g_t_logic_info |
logic/dispatch.h | g_t_dispatch_t.t_stsystemcmdinfo.u8ControlMode、AGING_MODE(46) |
hmi/key.h:229 | SYSTEM_DATETIME(RTC 年月日时分秒) |
data/system_config/system_config.h | g_t_system_base_info.u16_oem_flag(803)、OEM_HHE(57) |
data/data.h | g_t_ems_info.u_ems_fault.u32_all |
midware/inverter/inverter.h | g_t_inv_info.t_config_data.u8_inv_model_sub、g_t_inv_info.t_run_info.u16_mppt_percentage、INVERTER_TYPE、ALPHAESS_TYPE、SUB_S5、SUB_B5、SUB_S3_6、cUPS、PHASE_L1/L2/L3、PHASE_T |
| uCOS-II | OSTimeGet()(OS_TICKS_PER_SEC = 1000,即 1 tick = 1 ms) |
| 标准库 | abs()、memset(间接) |
6. 依赖关系
依赖
includes.h、ethernet.h、dry_node.h、bms.h、inverter.h、key.h、pcf8563.h、meter.h、logic.h、dispatch.h、system_config.h、data.h。其中 ethernet.h、pcf8563.h 在本文件中未见直接引用符号,属历史遗留 include。
被依赖
midware/interface/interface.c:221:唯一调度者(task_interface_parse,500 ms 级周期)。data/system_config/system_config.c:672、1798、3116:STDryNodeInfo的 Flash 默认值写入、读回、CRC 重算与保存(页号DRY_NODE_PARA_PAGE = 4078,data/system_config/system_config.h:30)。midware/interface/modbus/modbus.c(约 5068–5201、7876–8009):Modbus 寄存器映射读写。net/ems.c:1860–1875、2037–2048:云端上下行。net/server.c:3969–4006、4834–4849:本地 Web 参数表与 JSON 写入。
7. 备注与风险点
update_dry_node_data()的指针参数被完全忽略(dry_node.c:1117)、函数体直接写全局g_t_dry_node_ii_info1/2。调用点传&g_t_dry_node_ii_info1看似正确,但一旦有人复制函数复用到第三个实例就会静默写错对象。建议改为*t_dry_node_info = ...或去掉参数。check_dry_node_mode()存在"模式残留":所有else { //不执行 }路径不赋值,u8_cur_dry_node_mode保留上一次的值。配置切到AT_DISABLE/AT_DISABLE、或 SG-Ready 使能下选AT_MOST等未覆盖组合时,干接点会继续按旧模式动作,而不是回到DRY_NODE_NONE_MODE。- 新旧两代逻辑的跨天语义不一致:
Judge_By_Time()(dry_node.c:96)把start > end当跨天并正确处理,而judge_by_week_time()(dry_node.c:1326、1357)在start >= end时把区间判为"段内无效/段外",两套配置字段(u8StartTimeNAvsu16StartTimeMNA)语义不同。云平台下发跨天时段时行为会随新旧模式切换而变。 - 时间粒度不同:旧版按小时(
bHours),新版按分钟(bHours*60 + bMinutes)。旧版配置 8→18 实际是[8:00, 18:00),中间分钟无效——属于设计而非 bug,但配置文档需明确。 s16 l_s16ActPower = meter_read_actpower(...)截断(dry_node.c:151、637):meter_read_actpower返回s32,此处收窄到s16(±32767 W)。大功率站点(>32.7 kW)会溢出变号。而新版update_dry_node_data()用s32(dry_node.c:1144)没有这个问题——同一份数据两处类型不一致。- 魔法数:
l_s16ActPower >= 100(dry_node.c:363、411、849、897、1468、1513)、外置 NPE 的60V(dry_node.c:1705)、EPO 上电延时10*1000ms(dry_node.c:1817)、AT_DISABLE作为"关闭 SOC 判据"复用(dry_node.c:310、796)。建议补宏。 Get_Week_By_Date()的 u8 运算与写死世纪:Century = 20写死;l_u8Year = SYSTEM_DATETIME.bYears - 1在bYears == 0(2000 年 1/2 月)时按 u8 回绕成 255,算出星期错误(dry_node.c:64)。21 世纪其余年份中间值在 1~154 之间,赋值给 u8 不会溢出,但缺少注释说明这一巧合。Handle_Dry_Node_Logic()的 EPO 分支有副作用:g_t_logic_info.t_config_data.Generator = FALSE被写回配置结构(dry_node.c:1828),对 G3 单相机是"悄悄关掉柴油机配置",属于跨模块写配置,需确认云端不会因此报配置不一致。Dry_Node1_Logic/Dry_Node2_Logic在逻辑判定内部直接驱动 GPIO(dry_node.c:549、584、1035、1070),与driver_dry_node()是两条并行驱动路径。虽然Handle_Dry_Node_Logic()的 if/else 保证同周期只走一条,但Handle_Dry_Node_Logic()开头的柴油机/老化/EPO 分支已经可能驱动过DRY_NODE1,随后若配置回落到旧版逻辑分支,会在同一周期内二次改写。存在同一周期多次写同一 GPIO 的情况。s_u8_ups_flag1/2与s_u8_enable_flag1/2是函数内static,不是配置的一部分:这两组跳变标记在复位后靠上一周期值区分"由 UPS 掉出/由使能变禁用",掉电重启后不会恢复。DEBUG_DRY_NODE若误开为 1,Dry_Node1_Logic/Dry_Node2_Logic每个调用周期会执行 50+ 次printf,task_interface_parse是 500 ms 周期任务,会严重拖慢任务链(dry_node.h:18)。DRY_NODE_UPS_MODE、DRY_NODE_SGREADY_MODE、DRY_NODE_STATUS_NONE、DRY_NODE_STATUS_MAX在本文件中从未被使用,属预留/遗留枚举值。includes.h之外的ethernet.h、pcf8563.h未见使用,可清理。