EMS 知识库
首页 / knowledge/代码解析/logic

math_tools.c

源文件路径:logic/math_tools.c 所属模块:logic(能量调度核心,但实际是纯数据处理工具,不参与调度决策) 代码行数:137

1. 文件职责

本文件提供 EMS 固件里通用的「定点整数 ↔ 浮点字符串」互转工具,属于纯数据处理层,不参与任何调度或控制决策。fmt_fixed() 把以 0.01/0.1/0.001 等比例存储的定点整数量(电压、电流、功率、能量、频率)格式化成带小数点的字符串,用于拼装上报云端的 JSON 报文与存储 CSV 历史数据;parse_fixed() 反向把云端下发的浮点字符串(如 "3.3")解析为定点整数,用于安全参数配置和固件升级包参数;parse_dispatch_para() 专门解析调度指令 JSON 中 Parameter2":" 后面那串逗号分隔的功率参数。它处在数据流的「格式化/解析」旁路环节:上游是 net/ems.c(打包上报)、net/server.c(解析下行配置)、data/resume_data/flash/flash_data_store.c(写 CSV);下游是 TCP/UDP 报文缓冲区和 Flash。

2. 关键数据结构与宏

名称类型说明
DISPATCH_PARAMETER2宏(定义于本文件第 93 行)字面量 "Parameter2\":\"",即 JSON 中 Parameter2":" 的片段,用于定位调度参数字符串起点
s_au8FmtFixedBuf[16][16]函数内 static char 二维数组fmt_fixed() 的环形输出缓冲池,16 个槽 × 16 字节;每调用一次换一个槽,保证同一条 printf/snprintf 里多次调用结果不被覆盖
s_u8FmtFixedIdx函数内 static u8环形索引,每次调用后 (idx + 1) & 15,16 槽轮转
s64 / u32 / u8 / s32平台基础类型(chip.h / includes.h)见 system/sys/chip.h;本文件未定义任何结构体、枚举或联合体

本文件没有自定义结构体、枚举、位域;唯一宏 DISPATCH_PARAMETER2 定义在文件中部(第 93 行,parse_dispatch_para 之前),没有 #undef。

3. 函数清单

函数名作用域说明
fmt_fixed全局把定点整数按 scale 格式化为 "整数.小数" 字符串,返回指向 16 槽环形静态缓冲区的指针
parse_fixed全局把浮点字符串按 scale 解析为定点值,返回 u32(有符号语义在内部计算)
parse_dispatch_para全局从 Parameter2":" 片段起解析逗号分隔的数字列表到 out[],返回成功解析的字段个数

共 3 个函数,全部为全局作用域,无 static 函数、无回调、无函数指针。

4. 核心逻辑解析

4.1 fmt_fixed(s64 raw, u32 scale)

流程:

  1. 取环形缓冲当前槽 l_p_buf = s_au8FmtFixedBuf[s_u8FmtFixedIdx],随后 s_u8FmtFixedIdx = (s_u8FmtFixedIdx + 1) & 15;
  2. 用除 10 循环求 scale 的十进制位数:while (l_u32_scale > 1) { l_u8_digits++; l_u32_scale /= 10; }(scale=1→0 位,10→1 位,100→2 位,1000→3 位);
  3. 取绝对值:l_u32_abs = (raw < 0) ? (u32)(-raw) : (u32)raw;
  4. 拆分整数/小数部分:l_u32_ip = l_u32_abs / scale,l_u32_fp = l_u32_abs % scale;
  5. 格式化:snprintf(l_p_buf, 16, "%s%u.%0*u", (raw < 0) ? "-" : "", l_u32_ip, l_u8_digits, l_u32_fp);
  6. 返回缓冲指针。

要点:

4.2 parse_fixed(const char *s, u32 scale)

流程:

  1. 处理符号:'-' 置 l_s32_sign = -1 并前移,'+' 仅前移;
  2. 整数部分:while (*s >= '0' && *s <= '9') l_s32_ip = l_s32_ip * 10 + (*s - '0');(无位数限制,直接累加到 s32);
  3. 若遇 .:先 s++,再循环小数位。循环里维护 l_u32_rem_scale = scale:只要 rem_scale >= 10 就 rem_scale /= 10 并累加一位小数(l_s32_fp = l_s32_fp * 10 + digit);当 rem_scale 降到 1 后,剩余小数位只 s++ 不取值(截断)。循环结束后 l_s32_fp *= rem_scale 补齐位数;
  4. 返回 l_s32_sign * (l_s32_ip * scale + l_s32_fp)(表达式按 s32 计算,函数返回类型为 u32)。

解析示例(scale=100):"1.234" → 整数 1,小数位取两位 23,rem_scale 最终为 1,结果 1*100 + 23 = 123(截断到 1.23);"1.2" → 小数 2,rem_scale 降到 10,结果 100 + 2*10 = 120(即 1.20);"-0.5" → -(0 + 50) = -50。

要点与隐患:

4.3 parse_dispatch_para(const char *s, u32 *out, int max_fields)

流程:

  1. 前缀校验:strncmp(s, DISPATCH_PARAMETER2, sizeof(DISPATCH_PARAMETER2) - 1) != 0 则直接返回 0(sizeof - 1 正好是字面量长度 13,不含结尾 \0);
  2. 跳过前缀,进入字段循环,最多 max_fields 次:
    • 跳过空格 → 处理正负号 → 累加整数位(l_u8_got_digit 标记是否至少有一位数字);
    • 若无任何数字位 → break(结束解析);
    • 若遇 .,只 s++ 并跳过所有小数位(小数被完全丢弃,直接截断取整);
    • 写入 out[l_i_count] = l_s32_sign * l_s32_val 并 l_i_count++;
    • 若当前字符是 , 则 s++ 并继续下一个字段,否则 break;
  3. 返回解析成功的字段个数。

要点:

4.4 通信协议帧格式

本文件不涉及收发,无起始码/长度/命令字/校验;它只处理已经收到的报文中的数据字段。不过 DISPATCH_PARAMETER2 这个宏实际上固化了云端下发 JSON 的一个字段名约定:字段必须以 Parameter2 命名,且其值必须是 Parameter2":"<数字>[,<数字>...]" 的形式(无空格)。这是一处隐式协议契约,协议改名会直接导致解析返回 0,而调用方只会把它当作「该字段不存在」处理。

4.5 定时 / uCOS-II 配合

无。三个函数都是纯计算、无阻塞、不可重入风险点集中在 fmt_fixed 的静态缓冲区上(见备注 2)。本文件不调用任何 uCOS-II API,不使用信号量、事件标志或消息邮箱。

4.6 关键全局变量读写

无。本文件不读写 g_t_logic_info / g_t_ems_info 等全局业务结构,所有状态都是 fmt_fixed() 内的函数级 static 变量。

5. 对外接口

导出(供外部调用)——声明于 logic/math_tools.h:

名称主要调用者
fmt_fixed(s64, u32)net/ems.c(约 136 处,拼装上报 JSON)、data/resume_data/flash/flash_data_store.c(27 处,写 CSV 历史数据)
parse_fixed(const char *, u32)net/server.c(67 处,解析下行安全参数 / 配置,scale 取 1、10、100)
parse_dispatch_para(const char *, u32 *, int)net/server.c:806 / 810 / 814(解析 Parameter2 调度参数)

本文件不导出任何全局变量。

被调用(外部依赖):

名称来源
snprintfC 标准库 stdio.h(本文件显式 #include <stdio.h>)
strncmpC 标准库 string.h(本文件显式 #include <string.h>)
FALSE公共宏(经 chip.h → includes.h 链引入)
s64 / u32 / u8 / s32system/sys/chip.h

6. 依赖关系

依赖:

被依赖(include 点):

7. 备注与风险点

  1. 函数头注释的参数类型已过期:第 20 行写 const char *fmt_fixed(s32 raw, u32 scale),实际定义(26 行、头文件第 13 行)是 s64 raw;math_tools.h 用的是 s64。注释里的「raw 不能超过 32 位」才是真正有效的约束。
  2. fmt_fixed 的 16 槽静态缓冲区:整个函数不可重入、不线程安全,且在同一个表达式里调用超过 16 次会静默数据错乱(文件头注释用「数据会被踩掉!!!」强调过)。当前 net/ems.c 中单条 snprintf 里最多的调用次数不到 16(例如 8 个单体电流 + 8 个单体 SOC 分属两条语句),属于「刚好没越界」的状态;后续在此类语句中新增字段极易踩线,且失效表现为报文数值错乱而非崩溃,极难定位。建议改为由调用方传缓冲区,或至少增加「超过 16 次」的编译期/运行期保护。
  3. parse_fixed 返回类型与语义不符:返回值是 u32,但内部按有符号计算,负值回绕为接近 UINT32_MAX 的大数。net/server.c 中 67 处调用绝大多数用 l_u32_res 接收,只有少数用 l_s32_res。对功率/电流这类可正可负的配置项而言,用 u32 接收是明确的类型错误,只是恰好被后续 inverter_set_* 的形参类型「兜住」,一旦某个 setter 改成 u32 形参就会变成天文数字。
  4. parse_fixed 无溢出与合法性校验:整数位累加、l_s32_ip * scale 均可溢出;s 为 NULL 会直接解引用崩溃(函数内没有判空);非法输入(空串、.、纯符号)返回 0 且无错误码,调用方无法区分「真的解析到 0」和「解析失败」。
  5. parse_fixed / parse_dispatch_para 的小数一律截断且 scale 必须是 10 的幂:传入非 10 的幂(如 8、60)时小数部分被静默丢弃。所有调用点目前只用 1/10/100,属约定俗成而非强制。
  6. DISPATCH_PARAMETER2 宏定义在函数之间且与协议字段名重复:第 93 行的宏写死 "Parameter2\":\"",而协议字段名在 net/server.h:21 已定义为 FIELD_NAME_SPECIAL_CMD_PARA2 "Parameter2"。同一协议字面量在两处各自硬编码(且本文件还额外带上了 ":" 的 JSON 片段格式假设),协议改名时必须同时改两处,且本文件没有 #undef,宏在文件内全局可见,有与它处同名宏冲突的隐患。
  7. parse_dispatch_para 不校验 out 容量:max_fields 完全由调用方决定,函数直接写 out[l_i_count]。当前调用方传 8 而 f_para[8],刚好安全;新增调用点若传入 >8 会立即越界写 DISPATCH_CMD_T 相邻字段。
  8. parse_dispatch_para 的调用契约很脆弱:它要求传入的指针必须正好指向 Parameter2":" 的首字符,所以 server.c 不得不用 strstr() 先搜一遍再传进来;若 JSON 里 Parameter2 出现在 Parameter20 之类的相似字段之前,strstr 会命中错误位置(截断后的前缀比较仍然是真),从而解析出错误参数。同时字段值里带空格("1, 2, 3")可被容忍(循环开头会跳空格),但带引号/方括号(数组形式)会立刻停止解析。
  9. parse_dispatch_para 的返回值被当成格式判别码:== 8 / == 7 / == 4 三个分支决定调度指令是哪种格式。若云端下发的参数个数恰好是 5 或 6(不在这三个值里),三个分支全部失败,l_u8_recv_flag[1] 保持 0,整条指令被静默丢弃且无任何日志(server.c 里该处没有 else 分支的记录动作)。
  10. fmt_fixed 中 (u32)(-raw) 对 raw = INT64_MIN 是未定义行为,且任何绝对值超过 0xFFFFFFFF 的输入都会被静默截断。当前调用方传入的都是 u16/s16/u32 级别的量,实际不会触发,但类型契约上留有缺口,建议在函数入口做范围断言。
  11. (raw < 0) 判定用于取符号:raw 为 -0 不可能出现(整型无负零),但 scale 为 0 会导致 l_u32_ip 除零崩溃(l_u32_abs / scale)。函数内没有 scale == 0 保护,也没有对 scale 非 10 的幂的告警。