meter_download.c
u8_upgrade_object)的写入方与消费方4.2 get_Meter_Updt_File_Mask() 的空串兜底4.3 下载百分比公式与取值来源4.4 状态迁移(外部状态机,本文件提供读点与一个写点)4.5 通信协议帧格式与重试机制4.6 与 uCOS-II 的配合5. 对外接口6. 依赖关系7. 备注与风险点源文件路径:
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:2979 | meter_read_hard_version(get_meter_updt_object()) → 上传 checkUpdate 的 Hard_Version 字段 |
net/ems.c:3009 | meter_get_meter_modle(get_meter_updt_object()) → 上传 File_Type 字段 |
net/ems.c:3090 | 同上,但用于固件内容请求报文 |
net/server.c:3299 | check_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、:6864 | GetPercentUpgradeMeter() 按对象取升级百分比 |
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
- 分母
u32LenFile:由net/server.c:3318set_file_updating_info_for_dev()从云端 JSON 的Length解析,且必须落在(1024*5, SDRAM_3M)(5 KB ~ 3 MB)区间内,见net/server.c:3469-3472。 - 分子
u32OffsetFile:由net/server.c:3568increase_current_offset_file_updt()按包长递增(调用点net/server.c:3831)。 - 结束(含 MD5 失败)时清零:
net/upd_download.c:429无条件清零,另外net/upd_download.c:417在updType == UPDT_Meter且 MD5 匹配时会额外再清一次(这一处特殊处理是本类型独有的)。
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 通信协议帧格式与重试机制
本文件不构造/解析报文,无超时参数。相关外部契约:
- 上行:
net/ems.c:2945request_update_file_info()中电表分支输出Hard_Version = meter_read_hard_version(get_meter_updt_object())(ems.c:2979)与File_Type = meter_get_meter_modle(get_meter_updt_object())(ems.c:3009);请求由net/ems.c:3042fill_common_data(..., REQUEST_COMM, CMD_CHECK_UPDATE)加 7 字节头 + 2 字节校验。 - 下行包级校验(
net/server.c:3692起):FileMask(8 字符,与get_Meter_Updt_File_Mask()比对) →FileOffset(必须等于本地已下载偏移)→FileType(必须命中meter_get_meter_modle(get_meter_updt_object()),server.c:3708)→FileLength(≤LEN_REQ_FILE) →FileData(hex)。任一步失败立即return丢包,等云端超时重发。 - 整卷校验是 MD5(
net/upd_download.c:391-427),失败只置md5_error位。 get_Meter_Updt_File_Mask()参与 4 处掩码匹配(net/server.c:3469、:3708、:3764、:3803),因此掩码若为空串被兜底为"00000000",会匹配不上任何分支,导致包被丢弃——这是兜底值的实际后果。
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_Mask | net/ems.c:2977、:3007、:3088;net/server.c:3299、:3469、:3708、:3764、:3803 |
set_meter_updt_object | net/server.c:2434、:2440(唯一调用者) |
get_meter_updt_object | net/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 |
IsDownloadMeter | hmi/ui/ui_main.c:862;net/ems_download.c:259(IsSystemDownload()) |
GetPercentDownloadMeter | hmi/ui/ui_main.c:868 |
本文件不导出全局变量。
本文件调用的外部符号:
| 名称 | 来源 |
|---|---|
g_stEMSInfo | net/ems.h |
get_flag_force_update / get_curr_offset_file_request / get_length_file_updating | net/upd_download.c(声明于 upd_download.h:26/28/37) |
strlen | C 标准库(经 string.h 间接引入) |
FILE_MASK_DEFAULT | net/server.h:82 |
6. 依赖关系
依赖(第 13-19 行 include):
upd_download.h、meter_download.h、ems.h、server.h、MD5.h、sdram.h、iwdg.h- 其中
MD5.h、sdram.h、iwdg.h在本文件内无任何直接引用(MD5 计算在upd_download.c),是从本文件群复制模板时留下的冗余 include。
被依赖(include meter_download.h 的文件,共 9 处):
net/meter_download.c:14、net/ems.c:21、net/ems_download.c:20(写成了大写开头的"Meter_download.h",见备注 9)、net/server.c:23hmi/ui/ui_main.c:25midware/meter/meter.c:29、midware/meter/meter_upgrade.c:17、midware/meter/bb_update.c:17midware/interface/modbus/modbus.c:35
注意:net/meter_download.c 是 afci_download.c 中 get_afci_Updt_File_Mask() 的代码来源(两者实现逐字相同,仅下标不同),afci_download.c:43 的下标错误正是从本文件复制后未改下标所致。
7. 备注与风险点
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有同样问题)。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)校验。- 百分比函数存在除零可能:
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% 而非异常。 u8截断:返回u8而中间计算是u32。正常流程比值 ≤ 100;若偏移异常超过总长,结果会被截断成任意 8 位数。- 返回内部数组指针(非拷贝):
get_Meter_Updt_File_Mask()返回stUpt[UPDT_Meter].as8FileMask的地址,空串兜底时返回的却是字符串字面量(只读段)。调用方若尝试写入返回值,前者会破坏升级状态、后者会写入只读内存触发 HardFault。当前所有调用点都是只读使用,但接口本身没有const修饰(返回类型是char*而非const char*)。 @file注释与真实文件名不符:第 2 行写meter_download.c(此处一致),但第 3 行@author是Alpha g_stEMSInfo Team(与.h一致,与其它三个 download 文件的Alpha EMS Team不一致),@date为2017/05/18,是本文件群的共用历史日期。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用小写,可知这是笔误。get_Meter_Updt_File_Mask()的兜底值会导致丢包:掩码为空时返回"00000000"(FILE_MASK_DEFAULT),而net/server.c的所有掩码分支都不匹配"00000000",strstr全部失败,包会被直接return丢弃。兜底值在这里更像是"安全失败",但调用方无法区分"未下发掩码"和"掩码真的就是全 0"。- 冗余 include:
MD5.h/sdram.h/iwdg.h未被本文件使用,可移除(见 6. 依赖)。