sunspec_business.c
sunspec_modbus_data_init(1043 行)4.4 有功功率调度刷新 sunspec_active_power_dispatch_refresh(static,1079 行)4.5 运行数据总刷新 sunspec_modbus_data_refresh(1173 行)4.6 通信看门狗与 MPPT 辅助接口(1473~1600 行)5. 对外接口5.1 本文件导出(声明于 sunspec_business.h)5.2 本文件定义的全局变量5.3 本文件调用的外部函数/变量6. 依赖关系7. 备注与风险点源文件路径:
midware/interface/modbus/sunspec_business.c所属模块:midware/interface/modbus(Sunspec Modbus 协议应用层) 代码行数:1603
1. 文件职责
本文件是 Sunspec Modbus 从站协议在 EMS 侧的"业务数据层",负责把 EMS 内部的实时运行数据(逆变器、BMS、电表、并机、能量统计)翻译成 Sunspec 标准模型的数据结构,并把上位机(第三方调度器/网关)通过 Modbus 写下来的 Sunspec 控制点翻译回 EMS 的调度接口。
它在数据流中处于"寄存器映射表 ↔ 业务结构体"的中间环节:sunspec_modbus.c 里 783 条 g_t_sunspec_mudbus[] 地址表把 Sunspec 寄存器地址绑定到本文件定义/刷新的各个数据结构字段上,本文件提供按地址二分查找的读写入口 sunspec_modbus_data_get/set,供 modbus.c 的从站响应流程调用。
驱动方式有两种:一是由 midware/interface/interface.c:369 的 Modbus-RTU 任务在收到合法 Sunspec 帧后(sunspec_refresh_flag_set() 打点)周期调用 sunspec_modbus_data_refresh() 刷新全部运行数据;二是由 logic/ac_control.c、logic/logic.c 在充放电控制与 MPPT 调度逻辑中反向读取本文件导出的铭牌/限值/使能接口。
此外本文件还承载了"机型铭牌表"这一静态配置:14 种 AlphaESS 三代机型(G3-S5/S6/S8/B5/S3.6/T4/T5/T6/T8/T10/T12/T15/T20)的额定功率、无功能力、电压电流范围、控制模式位图等参数都硬编码在这里。
2. 关键数据结构与宏
2.1 本文件定义/初始化的对象
| 名称 | 类型 | 说明 |
|---|---|---|
g_sunspec_comm | 结构体变量(SUNSPEC_COMM_T) | Sunspec 通信看门狗状态,含 refresh_tick(最近一次合法帧的 OSTimeGet())与 refresh_flag。文件作用域,无 extern 声明,仅本文件内使用。 |
g_t_sunspec_inv_nameplate_array[] | 结构体数组(SUNSPEC_NAMEPLATE_T[SUNSPEC_INV_MAX],14 项) | 机型铭牌表,索引与 E_SUNSPEC_INV_TYPE 枚举一一对应(第 0 项 G3-S5 … 第 13 项 G3-T20 汇川)。 |
SUNSPEC_NAMEPLATE_T | 结构体(sunspec_modbus.h:182) | 铭牌数据结构:有功/视在/无功上限、充放电功率额定、AC 电压电流范围、功率因数、运行类别、supported_control_mode 位图、intentional_island,以及对应的 *_set 可写版本和 7 个 scale 系数。 |
SUNSPEC_COMM_T | 结构体(sunspec_modbus.h:430) | {u32 refresh_tick; u16 refresh_flag;}。 |
SUNSPEC_INV_MAX | 枚举末值(sunspec_modbus.h:98)= 14 | 铭牌数组长度。 |
2.2 本文件主要引用的外部结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
SUNSPEC_MODBUS_T | 结构体(sunspec_modbus.h:101) | 地址表元素:data_p(指向业务字段的 u16*)、addr、default_value、init_type、big_endian、scale、rw。本文件的 data_get/set 完全依赖它。 |
g_t_sunspec_mudbus[] | 全局数组(SUNSPEC_MODBUS_T[783],定义于 sunspec_modbus.c:49) | 783 条 Sunspec 地址映射,必须按 addr 升序,本文件据此做二分查找。 |
E_R_W | 枚举(sunspec_modbus.h:16) | RO=0x01 只读、WO=0x02 只写、RW=0x03 读写。data_get 用 (rw & RO)==RO 判读权限,data_set 用 (rw & WO)==WO 判写权限。 |
E_BIG_ENDIAN | 枚举(sunspec_modbus.h:22) | BIG_ENDIAN / LIT_ENDIAN;LIT_ENDIAN 时做高低字节互换。 |
E_SCALE_FACTOR | 枚举(sunspec_modbus.h:27) | SCALE_0_001=1 … SCALE_1=1000(代表 1 倍,不缩放)… SCALE_1000=1000000。 |
E_INIT_TYPE | 枚举(sunspec_modbus.h:78) | INIT 允许上电写默认值,RUNING 保留。 |
E_SUNSPEC_INV_TYPE | 枚举(sunspec_modbus.h:83) | 铭牌数组索引:SUNSPEC_G3_S5=0 … SUNSPEC_G3_T20_INO=13。 |
E_SUNSPEC_WSET_ENA | 枚举(sunspec_modbus.h:37) | 有功调度相关地址常量 WSET_ENA_40300…WSET_ENA_40310,以及 MAX_CHG_40260/MAX_DISCHG_40261。 |
SUNSPEC_CONTROL_T | 结构体(sunspec_modbus.h:252) | 控制点集合 g_t_sunspec_ctlAC,本文件读写其中的 WsetEna/WsetMod/Wset/WsetPct/WsetEnaRvrt/WsetRvrtTms/WsetRvrtRem/mppt_percentage/mppt_over_count/RvrtTms_refresh_*。 |
SUNSPEC_RUN_DATA_T | 结构体(sunspec_modbus.h:131) | 运行数据 g_t_sunspec_run,本文件刷新其绝大部分字段。 |
SUNSPEC_DC_T / SUNSPEC_DC_GROUP_T | 结构体(sunspec_modbus.h:327/313) | 直流侧模型 714,group[4] 实际只用 group[0](电池)与 group[1](PV)。 |
SUNSPEC_AC_METER_T | 结构体(sunspec_modbus.h:382) | 交/直流电表模型 203,含 meter_Evt 位域联合体。 |
SUNSPEC_METER_EVT_U | 联合体(sunspec_modbus.h:342) | 电表事件位域,本文件只置 M_EVENT_Missing_Sensor。 |
INVERTER_SUB_E | 枚举(midware/inverter/inverter.h:305) | 机型子类型 SUB_S5/SUB_B5/SUB_S6/SUB_S8/SUB_S3_6/SUB_T4/T5/T6/T8/T10/T12/T15/T20,用于选择铭牌项。 |
INVERTER_RUN_MODE_E(cWaitMode 等) | 枚举 | 逆变器运行模式,本文件映射为 Sunspec inv_sta(0=Off…7=Standby)。 |
POSITIVE_VALUE_OFFSET | 宏(logic/dispatch.h:16)= 32000 | 调度功率的正偏置,避免负数在无符号通道里传输。 |
EVERGEN_FUNC_START | 宏(logic/dispatch.h:19)= 1 | set_dispatch_enable() 的入参。 |
SUNSPECT_ACTIVE_MODE | 枚举(logic/dispatch.h:54) | 调度模式:Sunspec 有功功率调度。 |
DISPATCH_MODE | 枚举(logic/logic.h:51) | 顺序控制模式:外部调度模式。 |
PHASE_A/B/C/T、PHASE_ALL、PV_CHANAL_ALL | 宏 | 电表/逆变器读取的相别与通道选择。 |
ALPHAESS_TYPE、INV_SHUTDOWN | 宏 | 逆变器厂商与停机状态判断。 |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
sunspec_modbus_data_get | 全局 | 按 Sunspec 地址在 g_t_sunspec_mudbus[] 中二分查找,做权限校验、倍率换算、大小端调整后返回寄存器值。 |
sunspec_modbus_data_set | 全局 | 按 Sunspec 地址二分查找后校验写权限、倍率换算、大小端调整并写回业务变量;对 WSET_ENA_40300~40310 额外置位回退计时刷新标志。 |
sunspec_modbus_data_init | 全局 | Sunspec 数据初始化:清零 base/run 结构、填 mn/md/opt/ver 标识、把 init_type==INIT 的地址表项写成默认值。 |
sunspec_active_power_dispatch_refresh | static | 有功功率调度刷新:维护回退倒计时,把 Wset/WsetPct 换算成调度功率写入 g_t_dispatch_t,未使能时把 PV 放开到 100%。 |
sunspec_modbus_data_refresh | 全局 | Sunspec 运行数据总刷新:机型铭牌选择、逆变器状态、BMS/电表/PV/能量数据搬运到各 Sunspec 模型结构。 |
sunspec_modbus_dischg_get | 全局 | 返回铭牌 max_dischg_set(电池最大放电功率设定值)。 |
sunspec_modbus_chg_get | 全局 | 返回铭牌 max_charge_set(电池最大充电功率设定值)。 |
sunspec_modbus_Wset_ena_get | 全局 | 返回 g_t_sunspec_ctlAC.WsetEna(有功调度使能)。 |
sunspec_modbus_mppt_percentage_get | 全局 | 返回受 Sunspec 调度限制后的 MPPT 百分比。 |
sunspec_modbus_mppt_percentage_set | 全局 | 设置 MPPT 百分比(上限 10000,即 100%);值 >0 时清零 mppt_over_count。 |
sunspec_modbus_mppt_over_count_add | 全局 | "PV 功率过大"计数自增,饱和到 10。 |
sunspec_modbus_mppt_over_count_get | 全局 | 返回"PV 功率过大"计数。 |
sunspec_refresh_flag_set | 全局 | 收到合法 Sunspec 帧时置位通信标志并记录 OSTimeGet()。 |
sunspec_refresh_flag_get | 全局 | 读通信标志,超过 30000ms 未刷新则自动清零。 |
sunspec_modbus_max_charge_rat_get | 全局 | 返回铭牌 max_charge_rat(电池额定充电功率)。 |
sunspec_modbus_max_discharge_rat_get | 全局 | 返回铭牌 max_discharge_rat(电池额定放电功率)。 |
4. 核心逻辑解析
4.1 机型铭牌表(第 26~891 行)
g_t_sunspec_inv_nameplate_array[SUNSPEC_INV_MAX] 共 14 项,用指定初始化器逐字段赋值,顺序严格等于 E_SUNSPEC_INV_TYPE:
| 索引 | 机型 | 有功率 (W) | 视在 (VA) | 无功 (var) | 说明 |
|---|---|---|---|---|---|
| 0 | G3-S5 | 5000 | 10000 | 4000 | 单相 5kW |
| 1 | G3-S6 | 6000 | 11500 | 4800 | |
| 2 | G3-S8 | 8000 | 11500 | 6400 | |
| 3 | G3-B5 | 5000 | 10000 | 4000 | |
| 4 | G3-S3.6 | 3680 | 4000 | 2944 | |
| 5 | G3-T4 | 4000 | 4000 | 3200 | 三相 |
| 6 | G3-T5 | 5000 | 5000 | 4000 | |
| 7 | G3-T6 | 6000 | 6000 | 4800 | |
| 8 | G3-T8 | 8000 | 8000 | 6400 | |
| 9 | G3-T10 | 10000 | 10000 | 8000 | |
| 10 | G3-T12 VAVE | — | — | — | 商用三相 |
| 11 | G3-T15 VAVE | — | — | — | AlphaESS 本体 T15 |
| 12 | G3-T15 汇川 (INO) | — | — | — | 汇川版 T15 |
| 13 | G3-T20 汇川 (INO) | 20000 | 20000 | 16000 | 最大机型 |
除功率参数外,每项还统一带有:normal_operating = SUNSPEC_CAT_B、abnormal_operating = SUNSPEC_CAT_2、supported_control_mode = 0x1FFF(bit0~bit12 全支持)、intentional_island = 0x0003,以及 7 个缩放系数 actpower_scale=0 / factor_scale=-2 / apparentpower_scale=0 / reactpower_scale=0 / volt_scale=-1 / cur_scale=-1 / susceptance_scale=0。
注意 *_set 后缀字段是上位机可写的限值副本(对应地址 40252~40270,RW 权限),与只读的额定值(40228~40251,RO)分开存放。
4.2 地址二分查找读写(第 902~1031 行)
g_t_sunspec_mudbus[] 按 addr 升序排列,因此 sunspec_modbus_data_get/set 用标准二分查找把 O(n) 降到 O(log n)(SUNSPEC_STRU_NUM=783,约 10 次比较):
left = 0; right = stru_size - 1
while(left <= right):
mid = left + (right - left)/2 // 写成这种形式防 (left+right) 溢出
if(table[mid].addr == target) -> 命中
elif(table[mid].addr < target) left = mid + 1
else right = mid - 1
读路径 sunspec_modbus_data_get(902):
- 命中且
(rw & RO) == RO才继续,否则返回 0; scale == SCALE_1(1000)直接取*(data_p);否则l_f16_scale = scale / 1000,再l_u16_ret = (u32)l_u16_ret * l_f16_scale;- 若
big_endian == LIT_ENDIAN,做((v>>8)&0xFF)|((v<<8)&0xFF00)字节互换; - 未命中或无权读或未初始化时统一返回 0(函数开头
l_u16_ret = 0的"没找到地址返回0")。
写路径 sunspec_modbus_data_set(957):结构与读对称,权限判据换成 (rw & WO) == WO;命中后先按倍率反算、再按大小端互换,最后 *(data_p) = input_data。
写后的特殊处理(1003~1030 行):源码注释明确写了"个别字段需要在重复写入时做额外处理……否则这函数容易成屎山"。实现方式是一个 switch(addr),把 WSET_ENA_40300 … WSET_ENA_40310(含 WsetEna、WsetMod、Wset 高低字、WsetRvrt 高低字、WsetPct、WsetEnaRvrt、WsetRvrtTms 高低字,见 sunspec_modbus.c:350~360)统一映射为
g_t_sunspec_ctlAC.RvrtTms_refresh_flag = 1;
即:任何有功调度相关寄存器的写入都会重置本文件的回退计时基准,实际倒计时运算延后到 sunspec_active_power_dispatch_refresh() 里做。
4.3 数据初始化 sunspec_modbus_data_init(1043 行)
memset(&g_t_sunspec_base, 0, sizeof)
memcpy(mn, "AlphaESS", sizeof("AlphaESS")) // 厂商标识
memcpy(sunspec_star, "Suns", sizeof("Suns")) // "Suns" 魔数
memcpy(ver, EMS_SOFT_VERSION, ...) // 版本字符串(server.h:138 → VERSION_STRING)
sscanf(inverter_read_system_modle(), "%s", md) // 机型字符串
memcpy(g_t_sunspec_base_GRID.opt, "Export+Import", ...)
memcpy(g_t_sunspec_base_PV.opt, "Production", ...)
memset(&g_t_sunspec_run, 0, sizeof)
sunspec_modbus_mppt_percentage_set(10000) // MPPT 默认 100%
for i in [0, SUNSPEC_STRU_NUM):
if(table[i].init_type == INIT): *(table[i].data_p) = table[i].default_value
它被 4 个地方调用(都属于"使能 Sunspec / 上电 / 云端下发"路径):data/system_config/system_config.c:762(Modbus 配置落 EEPROM 之后)、midware/interface/interface.c:304(Modbus-RTU 任务入口,仅当 u8_sunspec_en == TRUE)、net/server.c:5172(云端下发 SunspecEN 打开时)、midware/interface/modbus/modbus.c:10217(本机 Modbus 写寄存器改变了 Sunspec 使能或从站地址时)。
4.4 有功功率调度刷新 sunspec_active_power_dispatch_refresh(static,1079 行)
这是本文件唯一的算法主体,分三段:
第 1 段:基准时间重置(1081~1095)
if (RvrtTms_refresh_flag == 1) {
RvrtTms_refresh_flag = 0;
if (WsetEna == 1 && WsetEnaRvrt == 1) {
WsetRvrtRem = WsetRvrtTms; // 重置剩余秒数
RvrtTms_refresh_tick = OSTimeGet(); // 记录基准
g_t_dispatch_t.t_stsystemcmdinfo.u32CmdGetTick = OSTimeGet();
} else {
WsetRvrtRem = 0;
}
}
第 2 段:回退倒计时(1097~1108)
if (WsetEna == 1 && WsetEnaRvrt == 1 && WsetRvrtRem > 0) {
u16 second = (OSTimeGet() - RvrtTms_refresh_tick) / 1000; // 已过秒数
WsetRvrtRem = (WsetRvrtTms > second) ? (WsetRvrtTms - second) : 0;
}
源码注释说明:工程里没有秒级定时器,所以用 OSTimeGet() 的时间差做"软件倒计时",每次调用重算而不是递减。因此 WsetRvrtRem 的刷新精度取决于本函数被调用的频率(Sunspec 上位机约 3s 一帧)。
第 3 段:调度下发(1110~1148)
if (WsetEna == 1) {
set_dispatch_enable(EVERGEN_FUNC_START);
dispatch_set_mode(SUNSPECT_ACTIVE_MODE);
logic_set_sequencecontrol_mode(DISPATCH_MODE);
if (WsetMod == 1) l_s32_ctrl_ac = Wset; // 绝对值模式
else l_s32_ctrl_ac = WsetPct * max_actpower / 100; // 百分比模式
g_t_dispatch_t.t_stsystemcmdinfo.u16ActivePower = l_s32_ctrl_ac + 32000; // POSITIVE_VALUE_OFFSET
g_t_dispatch_t.t_stsystemcmdinfo.u32CmdGetTick = OSTimeGet();
g_t_dispatch_t.t_stsystemcmdinfo.u32_runtime = 30; // 注释:"Sunspec协议3S调用一次,拍脑袋定为30S"
} else {
g_t_dispatch_t.t_stsystemcmdinfo.u32_runtime = 0; // 撤销调度
sunspec_modbus_mppt_percentage_set(10000); // PV 开到最大
}
set_dispatch_enable 的关闭分支(EVERGEN_FUNC_STOP)被注释掉了(1144 行),改以 u32_runtime = 0 让指令自然过期。
被注释掉的 1120~1136 行说明:原设计在回退窗口内应改用 WsetRvrt / WsetPctRvrt 参与计算,当前实现已被注释,即"回退值"字段(40304/40305/40307)只被记录、不生效。
4.5 运行数据总刷新 sunspec_modbus_data_refresh(1173 行)
由 Modbus-RTU 任务在每次收到合法 Sunspec 帧后调用,函数内按 Sunspec 模型分组刷新:
(a) 机型与铭牌(1185~1259)
if (s_u8_inv_type != get_inverter_sub()
&& get_inverter_sub() ∈ {SUB_S5,SUB_B5,SUB_S6,SUB_S8,SUB_S3_6,
SUB_T4,T5,T6,T8,T10,T12,T15,T20})
{
s_u8_inv_type = get_inverter_sub();
switch(...) → l_u8_sunspec_inv_type = SUNSPEC_G3_xxx;
// SUB_T15 特殊:get_inverter_type()==ALPHAESS_TYPE 取 T15_VAVE,否则 T15_INO
// SUB_T12 → SUNSPEC_G3_T12_VAVE
g_t_sunspec_inv_nameplate = g_t_sunspec_inv_nameplate_array[l_u8_sunspec_inv_type];
}
用 static s_u8_inv_type 做边沿检测,只在机型变化时拷贝一次铭牌(SUNSPEC_NAMEPLATE_T 约 100 字节)。
(b) 模型 1 通用数据(1260 行)
g_t_sunspec_default.operating_sta = (inverter_get_shutdown_sta() == INV_SHUTDOWN) ? 1 : 0;
(c) 模型 701/702 逆变器状态与并网信息(1273~1352)
运行模式到 Sunspec inv_sta 的映射(源码内附有标准枚举注释):
| EMS 运行模式 | Sunspec inv_sta |
|---|---|
cWaitMode / cSelfTest | 1 (Sleeping) |
cCheckMode | 2 (Starting) |
cNormalMode | 3 (MPPT) |
cByPassMode | 5 (Shutting Down) |
cUpdateArm / cFaultMode | 6 (Fault) |
cUPS | 7 (Standby) |
| default | 1 |
其余字段:inv_alarms = 0xFF(硬编码)、DER_mode = 0x00000003(bit0 电网跟随 + bit1 电网支撑,附标准位定义注释)、inv_reactive_energy_input/output = 0、ambient_temp/cabinet_temp = 0、throttle_source = 0x00000001(bit0 = 最大有功限制)、max_feedin_percent = g_t_logic_info.t_config_data.u8_max_feedin_percent、grid_connect = (g_t_meter.t_grid_info.u8_comm_lost_err == 0) ? 1 : 0、inv_phase_num = u8_inv_phase_num - 1(0 单相/1 裂相/2 三相)、inv_cur = inverter_read_grid_cur(PHASE_ALL)、pf = s8_run_pf_value / 10。
视在/无功功率按厂商分流:
ALPHAESS_TYPE → g_t_alphaess_comm_info.t_alphaess_run_info.s32_inv_apparent_power / s32_inv_reactive_power
其它(汇川 T20)→ g_t_inv_info.t_run_info.s32_inv_apparentpower_t / s32_inv_reactpower_t
(d) 模型 713 储能电池(1362~1365)
energy_rating = bms_read_max_system_storage_capacity()
energy_available = bms_read_current_actual_remaining_available_electricity()
soh = bms_read_single_soh(0) * 10
storage_sta = (bms_read_fault_code() == 0) ? 0 : 1
(e) 向控制层回写标志位(1367 行)
g_t_logic_info.t_config_data.u8_maxfeedin_offset_flag = g_t_sunspec_ctlAC.WsetEna; —— 把"是否由 Sunspec 接管有功"回写到逻辑层配置。
(f) 有功调度刷新:1369 行调用 sunspec_active_power_dispatch_refresh()。
(g) 模型 203 电表(1371~1381、1439~1449):分别把 GRID_METER 与 PV_METER 的 cur_total / apparent_power_a/b/c / apparent_power 搬进 g_t_sunspec_AC_meter 与 g_t_sunspec_PV_meter;用 static s_u8_meter_grid_find / s_u8_pv_grid_find 做电表发现状态的边沿检测,变化时把电表型号串拷进 g_t_sunspec_base_GRID.mn / g_t_sunspec_base_PV.mn;最后按 meter_read_connect_status() 置 M_EVENT_Missing_Sensor 位。
(h) 模型 714 直流(1384~1437)
DC_Ports_num 累加规则: inverter_get_pv_start_status() != 0 → +1;bms_get_ready_enable() != TRUE → +1
DC_total_power = inverter_read_pv_power(PV_CHANAL_ALL) + bms_read_total_volt()/100
DC_total_cur = inverter_read_pv_cur(PV_CHANAL_ALL) + bms_read_total_cur()/100
DCWhinj_total = l_u32_pv_yeild + energy_read_bat_chgrge()/10
DCWhAbs_total = energy_read_bat_dischg()/10
group[0] = 电池:PrtTyp=1(电池),id=0,id_str=s_type_splice,DCV/DCA = 总压/总流÷100,
DCW = inverter_read_bat_power(),DCWhinj/Abs = 充/放电量÷10,tmp = s16_high_temper,
DCSta = 有故障→3(ERROR);无故障→(工作状态==1);电池不在位→0(OFF)
group[1] = PV :PrtTyp=0(PV),id=1,id_str="PV",DCV/DCW/DCA 取逆变器 PV 表,
DCWhinj = energy_read_storage_pv_input()*10,DCWhAbs=0,tmp = 机温,
DCSta = (parallel_read_system_pv_status()==1)
group[2]("Generic Bidirectional"混合系统端口)整段被注释掉(1452~1464 行),对应 u8_system_mode == HYBIRD_SYSTEM_MODE 的判断也不再参与 DC_Ports_num 计数。
4.6 通信看门狗与 MPPT 辅助接口(1473~1600 行)
- 看门狗:
sunspec_refresh_flag_set()在modbus.c:8576每次收到目的地址等于g_t_modbus_config.u16_sunspec_addr且使能的 Modbus 帧时调用,写refresh_flag=1与refresh_tick=OSTimeGet()。sunspec_refresh_flag_get()在读时判断OSTimeGet() - refresh_tick > 30000(30s)则回写refresh_flag=0并返回 0。这是"上位机是否还在线"的唯一判据,interface.c:369用它决定是否刷新数据,logic.c:3003用它决定是否让 Sunspec 接管 MPPT。 - MPPT 百分比:
mppt_percentage单位是"万分之一"(10000 = 100%)。set接口把 ≥10000 的输入夹到 10000;只要结果 >0 就把mppt_over_count清零。 - PV 过大计数:
mppt_over_count只在<10时自增,作为饱和计数器;消费方在logic/ac_control.c:1860~1888——mppt_over_count >= 3→sunspec_modbus_mppt_percentage_set(0)(关 PV);- 调度功率回升 1000W 以上或电池允许充电功率增超 1000W → 重新
set(10000)(开 PV)。
- 铭牌 getter:
dischg_get/chg_get返回可写的_set字段,在ac_control.c:1862~1912用作充放电功率的硬上限;max_charge_rat_get/max_discharge_rat_get返回只读额定值,用于同一处判断。
5. 对外接口
5.1 本文件导出(声明于 sunspec_business.h)
| 符号 | 类型 | 调用方 |
|---|---|---|
sunspec_modbus_data_init | 函数 | data/system_config/system_config.c:762、midware/interface/interface.c:304、net/server.c:5172、midware/interface/modbus/modbus.c:10217 |
sunspec_modbus_data_refresh | 函数 | midware/interface/interface.c:370 |
sunspec_modbus_data_get | 函数 | midware/interface/modbus/modbus.c:8222(读寄存器响应) |
sunspec_modbus_data_set | 函数 | midware/interface/modbus/modbus.c:8265, 8320(写单/多寄存器) |
sunspec_modbus_dischg_get / sunspec_modbus_chg_get | 函数 | logic/ac_control.c:1869, 1912 |
sunspec_modbus_Wset_ena_get | 函数 | logic/logic.c:3003 |
sunspec_modbus_mppt_percentage_get | 函数 | logic/logic.c:3004 |
sunspec_modbus_mppt_percentage_set | 函数 | logic/ac_control.c:1883, 1887(本文件内部也调用) |
sunspec_modbus_mppt_over_count_add / _get | 函数 | logic/ac_control.c:1876, 1882 |
sunspec_refresh_flag_set | 函数 | midware/interface/modbus/modbus.c:8576 |
sunspec_refresh_flag_get | 函数 | midware/interface/interface.c:369、logic/logic.c:3003 |
sunspec_modbus_max_charge_rat_get / sunspec_modbus_max_discharge_rat_get | 函数 | logic/ac_control.c:1862~1865 |
5.2 本文件定义的全局变量
g_sunspec_comm(SUNSPEC_COMM_T)与 g_t_sunspec_inv_nameplate_array[SUNSPEC_INV_MAX] —— 两者都没有在头文件中 extern 声明,实际是本文件私有的。
5.3 本文件调用的外部函数/变量
- 逆变器:
get_inverter_sub、get_inverter_type、inverter_read_system_modle、inverter_get_shutdown_sta、inverter_get_pv_start_status、inverter_read_grid_cur、inverter_read_pv_volt/cur/power、inverter_read_bat_power、inverter_read_machine_temp(midware/inverter/inverter.h);parallel_read_system_pv_status(midware/inverter/parallel.h) - BMS:
bms_read_max_system_storage_capacity、bms_read_current_actual_remaining_available_electricity、bms_read_single_soh、bms_read_fault_code、bms_read_work_status、bms_read_total_volt、bms_read_total_cur、bms_get_ready_enable(midware/bms/bms.h) - 电表:
meter_read_current、meter_read_apparent、meter_read_connect_status(midware/meter/meter.h) - 能量统计:
energy_read_bat_chgrge、energy_read_bat_dischg、energy_read_storage_pv_input(midware/auxiliary/system_aux.h) - 逻辑/调度:
set_dispatch_enable、dispatch_set_mode(logic/dispatch.h)、logic_set_sequencecontrol_mode(logic/logic.h) - 数据与全局变量:
g_t_sunspec_*系列(sunspec_modbus.c)、g_t_inv_info、g_t_meter、g_t_bat_info、g_t_alphaess_comm_info、g_t_logic_info、g_t_dispatch_t - 系统:
OSTimeGet()(uCOS-II)
6. 依赖关系
依赖(本文件 include / 使用)
includes.h(UCOSII/uCOS-CONFIG/,uCOS-II 与标准库聚合头,提供OSTimeGet、memcpy、sscanf)sunspec_business.h、sunspec_modbus.h(本模块自身的类型定义与地址表声明)net/server.h(EMS_SOFT_VERSION)midware/inverter/inverter.h、midware/inverter/alphaess/alphaess_comm_usart.hlogic/logic.h(含DISPATCH_MODE)、logic/dispatch.h(经logic.h间接)midware/meter/meter.h、midware/bms/bms.h、midware/auxiliary/system_aux.h
被依赖(谁用到它)
midware/interface/modbus/modbus.c(从站读写入口,40 行 includesunspec_business.h)midware/interface/interface.c(Modbus-RTU 任务,调用 init 与 refresh)logic/ac_control.c、logic/logic.c(读铭牌限值、MPPT 百分比、使能状态)logic/dispatch.c(仅 include,本文件未在其中被引用)data/system_config/system_config.c、net/server.c(配置变更时触发 init)
7. 备注与风险点
- 倍率换算存在整数除法缺陷(第 936、990 行):
l_f16_scale = g_t_sunspec_mudbus[..].scale / 1000;两侧都是整型,先做整数除法再赋给float。当地址表使用SCALE_0_1(=100)时100/1000 == 0,倍率直接变 0。实测sunspec_modbus.c中有 3 处使用SCALE_0_1(165/166/167 行,地址 40115/40116/40117,逆变器 A 相电流与两路电网电压),这三处读出来恒为 0;写入同样恒为 0。SCALE_0_01、SCALE_0_001一旦被使用也会同样失效。 - 机型未落在白名单时铭牌不赋值(第 1185~1198 行):外层
if只在get_inverter_sub()属于 13 个机型之一时才进入并拷贝g_t_sunspec_inv_nameplate。若上电早期get_inverter_sub()仍是SUB_NULL,或未来新增机型(如SUB_S5_II、SUB_M10、SUB_S10、SUB_B10,见inverter.h:332~337)未同步加入清单,则铭牌保持全 0。由于sunspec_modbus_chg_get()会被ac_control.c:1869直接当作充电限值下限,铭牌为 0 会导致调节参数被压到 0,影响明显。switch 内的default分支(1252 行)在此结构下不可达。 - 回退(Rvrt)功能未生效(第 1120~1136 行):处理
WsetRvrt/WsetPctRvrt的逻辑被整段注释,倒计时算完后并未真正回退到回退值。寄存器 40304/40305/40307 只写不用,上位机若依赖 Sunspec 标准回退语义会得到错误行为。 l_u32_pv_yeild是死变量(第 1177、1401 行):声明并初始化为 0 后再未赋值,DCWhinj_total里l_u32_pv_yeild + ...这项恒为 0,PV 发电量实际只靠电池充电量凑数。- 能量缩放不一致(第 1401 vs 1434 行):
group[1].DCWhinj = energy_read_storage_pv_input() * 10,而同一函数内其他能量字段统一是/10(电池充放、总注入)。乘除方向不同,单位是否对齐需要核对 PV 电量接口的原始单位。 DC_total_power量纲可疑(第 1398 行):inverter_read_pv_power() + bms_read_total_volt()/100,把一个功率和一个(电压/100)相加,物理意义不成立,像是漏了乘电流。- 有符号到无符号 / 宽到窄截断(第 1371~1381 行):
meter_read_current返回s16、meter_read_apparent返回s32,而目标字段cur_total是u16、apparent_power*是s16。馈网工况下负电流会变成大正数,视在功率超过 32767 会截断。 - 铭牌表内的疑似笔误:
- G3-B5 与 G3-S3.6 的
over_excited_pf/under_excited_pf写成8(第 235/236、296/297 行),其余机型与它们自身的_set版本都是80,量级差 10 倍。 - G3-S8 的
specified_over_excited_pf_set与specified_under_excited_pf_set写成1(第 184/185 行),非 set 版本为80。 - G3-T4/T5/T6/T8 的
max_actpower_set分别写成 8000/8000/12000/12000(第 367、429、491、553 行)而max_actpower是 4000/5000/6000/8000,且 T5 出现max_actpower_set(8000) > max_apparentpower_set(5000)的不自洽组合。源码在这些行尾带----------标记,属于已知的临时状态,但仍是运行时可达的超限值。
- G3-B5 与 G3-S3.6 的
g_t_logic_info.t_config_data.u8_maxfeedin_offset_flag被每帧回写(第 1367 行):该字段位于t_config_data(配置数据)内,若该结构会被持久化到 EEPROM,则 Sunspec 每次写WsetEna都可能引发一次掉电存储,需确认其是否在保存清单内。- magic number 集中:
30000(通信超时 ms,1573 行)、30(调度运行时长 s,1140 行)、10000(MPPT 100%,1061/1147/1519/1522/1887)、0xFF(inv_alarms,1310)、0x00000003(DER_mode,1312)、0x00000001(throttle_source,1338)、32000(POSITIVE_VALUE_OFFSET,1138)、10(过计数饱和,1538)、/100(电压电流缩放,1398/1399/1408/1410)、*10、/10(能量缩放)。注释里对 30s 的说明是"拍脑袋定为30S"。 sscanf(inverter_read_system_modle(), "%s", g_t_sunspec_base.md)(第 1055 行)没有宽度限制,若md[32]对应的机型串异常长会越界;memcpy(g_t_sunspec_base.ver, EMS_SOFT_VERSION, sizeof(EMS_SOFT_VERSION))(1053 行)也依赖版本串不超ver[16](当前"V0.11.676"为 10 字节,安全,但加长后缀后需复核)。inv_phase_num = u8_inv_phase_num - 1(第 1341 行):u8型减 1 后赋给u16字段,若配置值为 0 会得到 65535。group[2](混合系统双向端口)整段注释(1452~1464 行),DC_Ports_num的HYBIRD_SYSTEM_MODE计数分支同样被注释(1391~1395 行),混合系统场景下 714 模型端口数可能偏低,属于版本遗留代码。WSET_ENA_40300的switch只覆盖有功相关地址,源码注释也承认这种"命中后 switch 特判"的写法不可扩展("以后多了建议在结构体中加入函数指针处理"),是后续维护的负担点。