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

meter_download.c

源文件路径:net/meter_download.c 所属模块:net(网络应用层 / 设备固件升级下载) 代码行数:94

1. 文件职责

本文件是电表(Meter)云端固件下载流程的适配层,一共 5 个函数:get_Meter_Updt_File_Mask() 返回本次要升级哪类电表的文件掩码、set_meter_updt_object() / get_meter_updt_object() 读写“升级对象”(电网表 / 光伏表 / 正泰无线表等)、IsDownloadMeter() 查询强制下载标志、GetPercentDownloadMeter() 计算下载百分比。

它比同族的 bms/evc/afci_download.c 多出一项职责——维护升级对象状态。因为工程里有多种电表型号共用一个 UPDT_Meter 升级槽位,必须靠 u8_upgrade_object 区分:该值决定 meter_get_meter_modle()(机型串,用于云端匹配)与 meter_read_hard_version()(硬版本,用于上报)返回哪块表的信息(见 net/server.c:3299、net/ems.c:2979、net/ems.c:3009)。

数据流位置:云端特殊命令 → net/server.c:2346 handle_special_cmd_for_dev() 的 updType == UPDT_Meter 分支解析掩码与升级对象(server.c:2427-2442)→ 本文件被 net/ems.c / net/server.c / midware/meter 读取,用于机型比对、协议上报、电表刷写 → HMI 用 IsDownloadMeter() / GetPercentDownloadMeter() 显示进度。

驱动方是云端指令(写侧)、EMS 上报与电表升级模块(读侧)、HMI(显示)。

2. 关键数据结构与宏

名称类型说明
STUpdateTab结构体定义于 net/server.h:216;本文件读写其中的 as8FileMask[8](文件掩码)、u8_upgrade_object(升级对象)、bIsUpdateForce、u32LenFile、u32OffsetFile
ENUpdateType枚举定义于 net/server.h:186;本文件使用 UPDT_Meter(值 5,位于 UPDT_INV3 与 UPDT_EVC 之间)作为 stUpt[] 下标
ST_EMS_INFO(g_stEMSInfo)全局结构体实例定义于 net/ems.h;stUpt[UPDT_MAX] 是升级表(net/ems.h:346)
FILE_MASK_DEFAULT宏net/server.h:82,值 "00000000";get_Meter_Updt_File_Mask() 在掩码为空串时返回它
FILE_MASK_METER宏net/server.h:89,值 "00001000";电表(一代普通表)掩码,由 net/server.c:2430 写入
FILE_MASK_BB宏net/server.h:88,值 "00000020";电表盒 / BB(BlackBox)掩码,由 net/server.c:2439 写入
METER_SITE_E枚举定义于 midware/meter/meter.h:163:GRID_METER = 1(电网表)、PV_METER(光伏表)、CHNT_WIRELESS(正泰无线表)、MID_METER、MAX_METER_SITE;这正是 u8_upgrade_object 的取值范围
LEN_FILE_MASK_CODE宏net/server.h:144,值 (8);与 as8FileMask[8] 长度一致

本文件不定义任何结构体、枚举、联合体、位域或宏常量。

3. 函数清单

函数名作用域说明
get_Meter_Updt_File_Mask全局meter_download.c:40。返回 stUpt[UPDT_Meter].as8FileMask,为空串时回退 FILE_MASK_DEFAULT("00000000")
set_meter_updt_object全局meter_download.c:58。把入参写入 stUpt[UPDT_Meter].u8_upgrade_object,无范围校验
get_meter_updt_object全局meter_download.c:69。读取 stUpt[UPDT_Meter].u8_upgrade_object
IsDownloadMeter全局meter_download.c:80。返回 get_flag_force_update(UPDT_Meter),云端下发的电表强制下载标志
GetPercentDownloadMeter全局meter_download.c:91。返回 已下载偏移 * 100 / 文件总长

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

4. 核心逻辑解析

4.1 升级对象(u8_upgrade_object)的写入方与消费方

u8_upgrade_object 是 STUpdateTab 中唯一由本文件提供读写的字段,它的完整生命周期如下:

写侧只有一个入口:net/server.c:2427-2441

if (updType == UPDT_Meter) {
    if (strstr((char *)g_au8FWBuf, SPECIAL_CMD_UPDATE_METER) != NULL) {
        set_updt_file_mask(updType, FILE_MASK_METER);          // "00001000"
        if (json_get_value(..., FIELD_NAME_SPECIAL_CMD_PARA3, ...))
            set_meter_updt_object(atoi((char *)g_au8FWBuf));   // 云端指定对象编号
    } else {
        set_updt_file_mask(updType, FILE_MASK_BB);             // "00000020"
        set_meter_updt_object(GRID_METER);                     // 固定为电网表(1)
    }
}

即:云端 URL 里若带 SPECIAL_CMD_UPDATE_METER 关键字,则从 JSON 的 PARA3 字段 atoi 出对象编号;否则视为电表盒(BB)升级,对象固定为 GRID_METER。

读侧有 8 处,全部用于“把对象编号翻译成机型/版本”:

读点用途
net/ems.c:2979meter_read_hard_version(get_meter_updt_object()) → 上传 checkUpdate 的 Hard_Version 字段
net/ems.c:3009meter_get_meter_modle(get_meter_updt_object()) → 上传 File_Type 字段
net/ems.c:3090同上,但用于固件内容请求报文
net/server.c:3299check_server_fw_is_updating_for_dev(..., get_Meter_Updt_File_Mask(), meter_get_meter_modle(get_meter_updt_object()), NULL)
net/server.c:3708包级机型一致性比对
midware/meter/meter.c:5731、:5745、:5759按对象分派刷写流程
midware/meter/meter.c:6838、:6851、:6864GetPercentUpgradeMeter() 按对象取升级百分比
midware/meter/meter_upgrade.c:294、:298、:302、:436、:442电表刷写状态机按对象分支

风险点:set_meter_updt_object() 不做范围校验,atoi 的返回值(可能为 0、负数、>4)被直接当成 1 字节写入。后续 meter_read_hard_version(site) / meter_get_meter_modle(site) 的 site 参数若被当作数组下标使用,越界值会带来未定义行为(详见备注 2)。

4.2 get_Meter_Updt_File_Mask() 的空串兜底

char* get_Meter_Updt_File_Mask(void)                        // meter_download.c:40
{
    if(strlen(g_stEMSInfo.stUpt[UPDT_Meter].as8FileMask))
        return g_stEMSInfo.stUpt[UPDT_Meter].as8FileMask;
    else
        return FILE_MASK_DEFAULT;                           // "00000000"
}

返回的是内部数组的指针(非拷贝),因此调用方拿到的是"活动视图":一旦 set_updt_file_mask() 改写了掩码,先前保存的指针指向的内容也会变。当前所有调用点都是"取到即用"(net/ems.c:2977、:3007、:3088、net/server.c:3299、:3469、:3708、:3764、:3803),没有跨函数长期持有,故未暴露问题。

4.3 下载百分比公式与取值来源

u8 GetPercentDownloadMeter(void)
{
    return get_curr_offset_file_request(UPDT_Meter) * 100 / get_length_file_updating(UPDT_Meter);
}   // meter_download.c:91-94

4.4 状态迁移(外部状态机,本文件提供读点与一个写点)

[云端特殊命令] server.c:2346 handle_special_cmd_for_dev(..., UPDT_Meter, ...)
      ├─ server.c:2408/2411  置/清 bIsUpdateForce
      ├─ server.c:2418       set_updt_file_mask(UPDT_Meter, fileMask)
      └─ server.c:2427-2442  本文件 set_meter_updt_object(...)  ← 唯一写点
             ↓  IsDownloadMeter()==1 → HMI 进入 “Download Meter FW” 界面
      ↓  server.c:3469 set_file_updating_info_for_dev(UPDT_Meter, 5K, SDRAM_3M)
      ↓  server.c:3766 save_fw_to_spi_flash(..., UPDT_Meter) → save_fw_to_sdram(..., g_u8_fw_sdram1)
      ↓  server.c:3805 calculate_dev_FW_Flash_MD5(UPDT_Meter, g_u8_fw_sdram1, METER_FIRM_DOWNLOAD)
      ├─ MD5 一致 → upd_download.c:412 Set_Status_Is_Updated_From_Server(UPDT_Meter, TRUE)
      │             并额外 upd_download.c:417 把 u32OffsetFile 再清 0
      └─ MD5 不一致 → upd_download.c:422 u16_upgrade_error.BIT.md5_error = 1
             ↓  之后由 midware/meter 的 IsUpgradeMeter() / GetPercentUpgradeMeter() 接管刷写

4.5 通信协议帧格式与重试机制

本文件不构造/解析报文,无超时参数。相关外部契约:

4.6 与 uCOS-II 的配合

本文件不含任何 uCOS-II 调用:无任务创建、无信号量、无消息邮箱、无延时。5 个函数都是对 g_stEMSInfo 的同步单次读写。

stUpt[UPDT_Meter] 由 EMS 网络任务写(net/server.c)、被 HMI 任务与电表任务(midware/meter/*)读,无临界区保护。u8_upgrade_object 的“读-翻译-使用”序列(如 net/ems.c:2979 先读对象再查机型)跨两次调用,若此时云端恰好下发新的升级对象,会用旧对象对应新机型串,产生一次不一致的上报(下一轮恢复)。

5. 对外接口

本文件导出(供谁调用):

导出名调用方
get_Meter_Updt_File_Masknet/ems.c:2977、:3007、:3088;net/server.c:3299、:3469、:3708、:3764、:3803
set_meter_updt_objectnet/server.c:2434、:2440(唯一调用者)
get_meter_updt_objectnet/ems.c:2979、:3009、:3090;net/server.c:3299、:3708;midware/meter/meter.c:5731、:5745、:5759、:6838、:6851、:6864;midware/meter/meter_upgrade.c:294、:298、:302、:436、:442
IsDownloadMeterhmi/ui/ui_main.c:862;net/ems_download.c:259(IsSystemDownload())
GetPercentDownloadMeterhmi/ui/ui_main.c:868

本文件不导出全局变量。

本文件调用的外部符号:

名称来源
g_stEMSInfonet/ems.h
get_flag_force_update / get_curr_offset_file_request / get_length_file_updatingnet/upd_download.c(声明于 upd_download.h:26/28/37)
strlenC 标准库(经 string.h 间接引入)
FILE_MASK_DEFAULTnet/server.h:82

6. 依赖关系

依赖(第 13-19 行 include):

被依赖(include meter_download.h 的文件,共 9 处):

注意:net/meter_download.c 是 afci_download.c 中 get_afci_Updt_File_Mask() 的代码来源(两者实现逐字相同,仅下标不同),afci_download.c:43 的下标错误正是从本文件复制后未改下标所致。

7. 备注与风险点

  1. as8FileMask[8] 可能无结束符,strlen 存在越界读:set_updt_file_mask()(net/upd_download.c:253)用 strncpy(dst, fileMask, 8) 写入,net/server.c:3322 又是 memcpy 取 min(strlen, 8)。掩码串字面量恰好是 8 字符("00001000"、"00000020"),末尾 '\0' 会被截掉,meter_download.c:42 的 strlen 会继续读到结构体下一成员 as8FWType[10]。当前靠该成员恰好为 0 才得到正确结果,属未定义行为(afci_download.c:43 有同样问题)。
  2. set_meter_updt_object() 无范围校验:meter_download.c:58-61 直接把入参写入 u8。写侧 net/server.c:2434 是 set_meter_updt_object(atoi((char *)g_au8FWBuf)),atoi 对非法串返回 0、对超长数字会回绕,可能写入 0(非法,METER_SITE_E 从 1 开始)或大于 MAX_METER_SITE(5)的值。该值随后被当作 meter_read_hard_version(site) / meter_get_meter_modle(site) 的参数(net/ems.c:2979、:3009),若这两个函数内部以 site 索引数组则存在越界风险。建议在此函数加 if (object >= GRID_METER && object < MAX_METER_SITE) 校验。
  3. 百分比函数存在除零可能:meter_download.c:93 的除数是 u32LenFile。bIsUpdateForce 在 net/server.c:2408 置位时文件长度尚未解析(值为 0),HMI 的调用顺序是「先判 IsDownloadMeter() 再算百分比」(ui_main.c:862、:868),存在分母为 0 的窗口。ARM 平台上 32 位无符号除法除零不触发硬件异常(UDIV 或库函数 __aeabi_uidiv 均返回 0),表现为显示 0% 而非异常。
  4. u8 截断:返回 u8 而中间计算是 u32。正常流程比值 ≤ 100;若偏移异常超过总长,结果会被截断成任意 8 位数。
  5. 返回内部数组指针(非拷贝):get_Meter_Updt_File_Mask() 返回 stUpt[UPDT_Meter].as8FileMask 的地址,空串兜底时返回的却是字符串字面量(只读段)。调用方若尝试写入返回值,前者会破坏升级状态、后者会写入只读内存触发 HardFault。当前所有调用点都是只读使用,但接口本身没有 const 修饰(返回类型是 char* 而非 const char*)。
  6. @file 注释与真实文件名不符:第 2 行写 meter_download.c(此处一致),但第 3 行 @author 是 Alpha g_stEMSInfo Team(与 .h 一致,与其它三个 download 文件的 Alpha EMS Team 不一致),@date 为 2017/05/18,是本文件群的共用历史日期。
  7. net/ems_download.c 以错误大小写包含本头文件:net/ems_download.c:20 写的是 #include "Meter_download.h"(首字母大写),而真实文件名是 meter_download.h。Windows / Keil MDK 的文件系统不区分大小写,因此能正常编译;一旦迁移到区分大小写的工具链(Linux 上的 GCC、大小写敏感的 Git 检出),该处会直接报"找不到文件"。对比 net/ems_download.c:30 包含 afci_download.h 用小写,可知这是笔误。
  8. get_Meter_Updt_File_Mask() 的兜底值会导致丢包:掩码为空时返回 "00000000"(FILE_MASK_DEFAULT),而 net/server.c 的所有掩码分支都不匹配 "00000000",strstr 全部失败,包会被直接 return 丢弃。兜底值在这里更像是"安全失败",但调用方无法区分"未下发掩码"和"掩码真的就是全 0"。
  9. 冗余 include:MD5.h / sdram.h / iwdg.h 未被本文件使用,可移除(见 6. 依赖)。