9ceb218f80
Co-authored-by: Cursor <cursoragent@cursor.com>
9.2 KiB
9.2 KiB
LwIP FTP 升级功能说明(最终版)
1. 设计目标
- 在 不依赖 4G 模块 AT 指令 的情况下,通过 以太网 (LwIP socket) 实现 FTP 固件下载。
- 复用已有的
flash_upgrade.h升级区管理:upgrade_erase_program_area()擦除升级区域;upgrade_write_packet()按PACKET_SIZE(1024 字节)分包写入;upgrade_success_update_header()更新升级头(UPGRADE_FLAG_IDLE / UPGRADE_FLAG_NEED_UP)。
- 与 4G FTP 升级整体思路保持一致,但实现形式为:
非阻塞状态机 + 周期调用v_ftp_update_action()。
2. 模块文件结构
位于 app/plat_comm/lwip_module/:
-
lwip_module.[ch]- 对 LwIP socket 做简单封装:
lwip_resolve_host / lwip_set_nonblock / lwip_tcp_send / lwip_tcp_recv / lwip_tcp_close等。
- 对 LwIP socket 做简单封装:
-
lwip_ftp_upgrade.[ch]- 网线 FTP 程序远程升级的完整状态机实现,仅保留这一对文件:
- 控制连接(21 端口);
USER/PASS登录;TYPE I二进制模式;CWD(可选目录切换);SIZE查询文件大小;PASV被动模式解析数据通道;RETR下载文件并按 1024 字节分包写 Flash;QUIT结束会话并写入 NEED_UP 升级头。
- 对外只暴露两个接口:
void lwip_ftp_upgrade_init_from_tftp(void);int lwip_ftp_upgrade_step(void);
- 网线 FTP 程序远程升级的完整状态机实现,仅保留这一对文件:
说明:旧版的
lwip_ftp_fw_upgrade_api.[ch]、lwip_ftp_upgrade_flash_adapter.[ch]、lwip_ftp_fw_upgrade_sm.[ch]等文件已删除,不再使用。
3. 参数来源与触发方式
3.1 参数结构:T_FTP_UPDATE_INFO
在 bs_public_impl.h 中定义,关键字段如下:
u8_update_server_addr:FTP 服务器域名 / IP;u16_update_server_port:FTP 端口(通常 21);u8_user / u8_pwd:用户名、密码;u8_path:远程目录(可为空);u8_file_name:文件名;u8_netType:网络类型(0=4G,1=网线);u32_file_size:文件大小(由 FTP SIZE 结果回填);u8_update_result:升级结果(0=成功,1=失败)。
这些字段由 OCPP UpdateFirmware 命令解析函数 parse_ftp_url() 填充。
3.2 触发流程
- OCPP 平台下发
UpdateFirmware,URL 形如:
ftp://user:pass@host:port/path/file.bin
→ 解析到全局t_ftp_update_info。 - 上层把对应平台的
u8_updata_flag置位。 plat_comm_task.c中的v_Plat_Comm()周期调用v_ftp_update_action()。v_ftp_update_action()内部根据:s_bs_task_ctrl.ocpp_data.u8_log_file_upload:1=日志上传,0=程序升级;t_ftp_update_info.u8_netType:0=4G(走 4G 模块),1=网线(走 LwIP FTP)。
4. 程序升级流程(网线 / LwIP)
当满足:
s_bs_task_ctrl.ocpp_data.u8_log_file_upload == 0(程序升级);t_ftp_update_info.u8_netType != 0(网线/LwIP);
v_ftp_update_action() 中流程为:
if (t_ftp_update_info.u8_init_flag == 0)
{
if (t_ftp_update_info.u8_netType == 0) {
v_ftp_ctrl_init(); // 4G 模块初始化
} else {
lwip_ftp_upgrade_init_from_tftp(); // 网线 FTP 状态机初始化
}
t_ftp_update_info.u8_init_flag = 1;
}
if (t_ftp_update_info.u8_netType == 0) {
u8_ec200a_4g_ftp_update(); // 4G FTP 升级
} else {
int ret = lwip_ftp_upgrade_step(); // LwIP FTP 升级状态机单步
if (ret == 1) {
// 升级成功
s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag = 0;
s_bs_task_ctrl.s_bs_ctrl[1].u8_updata_flag = 0;
t_ftp_update_info.u8_update_result = 0;
} else if (ret < 0) {
// 升级失败
s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag = 0;
s_bs_task_ctrl.s_bs_ctrl[1].u8_updata_flag = 0;
t_ftp_update_info.u8_update_result = 1;
}
}
4.1 状态机步骤(E_LWIP_FTP_STEP)
lwip_ftp_upgrade.c 内部定义的状态机大致流程:
LWIP_FTP_STEP_CONNECT- 建立 FTP 控制连接(21 端口),并设置为非阻塞。
LWIP_FTP_STEP_WAIT_WELCOME- 等待
220欢迎语。
- 等待
LWIP_FTP_STEP_SEND_USER / WAIT_USER- 发送
USER xxx,等待响应; - 若返回
331,继续发送PASS;否则直接进入TYPE。
- 发送
LWIP_FTP_STEP_SEND_PASS / WAIT_PASS- 发送
PASS xxx,等待登录结果。
- 发送
LWIP_FTP_STEP_SEND_TYPE / WAIT_TYPE- 发送
TYPE I,切换为二进制模式。
- 发送
LWIP_FTP_STEP_SEND_CWD / WAIT_CWD(可选)- 如果
u8_path非空,发送CWD <path>切目录。
- 如果
LWIP_FTP_STEP_SEND_SIZE / WAIT_SIZE- 发送
SIZE <file>,并从213响应中解析文件大小; - 回填到
s_ctx.file_size和t_ftp_update_info.u32_file_size。
- 发送
LWIP_FTP_STEP_PREPARE_FLASH- 调用
upgrade_erase_program_area()擦除升级区; - 构造
upgrade_header_t,写入UPGRADE_FLAG_IDLE / file_size / packet_count等信息。
- 调用
LWIP_FTP_STEP_SEND_PASV / WAIT_PASV- 发送
PASV,解析227响应得到数据通道 IP/端口。
- 发送
LWIP_FTP_STEP_CONNECT_DATA- 按
data_ip / data_port建立数据连接 socket。
- 按
LWIP_FTP_STEP_SEND_RETR / WAIT_RETR- 发送
RETR <file>,等待150/125等开始传输响应。
- 发送
LWIP_FTP_STEP_RECV_DATA- 循环接收数据,按 1024 字节分包写入 Flash(见 4.2)。
LWIP_FTP_STEP_SEND_QUIT- 发送
QUIT,关闭控制连接; - 写入
UPGRADE_FLAG_NEED_UP头部,标记升级就绪。
- 发送
LWIP_FTP_STEP_DONE / FAILDONE:整个流程成功,lwip_ftp_upgrade_step()返回1;FAIL:任意步骤出错,返回<0。
4.2 分包写 Flash 与进度打印
- 内部使用一个 动态申请的 1024 字节缓存
packet_buf(不是全局大数组):- 在
lwip_ftp_upgrade_init_from_tftp()中第一次调用pvPortMalloc(PACKET_SIZE)(FreeRTOS 堆),后续重复使用; - 失败会直接打印
"pvPortMalloc packet_buf fail"并进入FAIL。
- 在
- 数据通道每次
lwip_tcp_recv()得到的数据都会写入packet_buf:- 每累计满 1024 字节调用一次
upgrade_write_packet(packet_index, packet_buf, len); - 处理最后不足一包的数据时,也会单独调用一次。
- 每累计满 1024 字节调用一次
- 接收进度打印:
- 每次收到数据后,都会根据
downloaded / file_size计算百分比:Plat_Comm_LOG("lwip ftp: recv %lu/%lu (%u%%)\r\n", ...); - 方便在串口日志中实时观察下载进度。
- 每次收到数据后,都会根据
4.3 控制通道响应等待(最长 1 秒)
-
所有 FTP 控制命令的响应(
220 / 331 / 230 / 213 / 227 / 150 ...)统一通过:static int ftp_read_reply_block(int sock, uint32_t timeout_ms, int *out_code, char *out_msg, size_t out_msg_sz); -
关键特性:
- 内部使用局部缓冲区:
char rx_buf[FTP_CTRL_RX_BUF_SIZE];char line[FTP_LINE_MAX];- 不再在全局结构体中定义大数组。
- 每次调用最长阻塞
timeout_ms毫秒(本工程中统一传入1000,即 1 秒):- 在 1 秒内尽可能收满一条完整响应行;
- 超时、对端关闭或错误均返回非 0,状态机进入
FAIL。
- 内部使用局部缓冲区:
-
满足需求:
- “发送命令后可最长等待 1 秒接收数据,这样可以保证大部分交互在一个周期内完成”。
5. 日志上传(4G / 预留 LwIP)
- 当
s_bs_task_ctrl.ocpp_data.u8_log_file_upload == 1时,v_ftp_update_action()走“日志上传”分支:- 当前仅在
t_ftp_update_info.u8_netType == 0(4G)时调用u8_ec200a_4g_ftp_log_upload(); - 网线场景下会打印提示,尚未提供 LwIP 日志上传实现。
- 当前仅在
后续如需支持 LwIP FTP 日志上传,建议在 lwip_ftp_upgrade.[ch] 基础上扩展 STOR/APPE 等命令。
6. 注意事项
-
非阻塞总框架 + 局部 1 秒等待:
- 整体采用状态机 + 周期调用的方式集成在
v_Plat_Comm中,不新建线程; - 单条 FTP 命令的响应等待时间 最多 1 秒,保证大部分交互能在一次
v_ftp_update_action()周期内完成。
- 整体采用状态机 + 周期调用的方式集成在
-
内存使用:
- 不再在全局结构体中定义大数组:
- 控制通道解析使用的缓冲区全部为函数内局部变量;
- 数据分包缓冲
packet_buf[PACKET_SIZE]采用 FreeRTOSpvPortMalloc动态申请,仅 1KB。
- 不再在全局结构体中定义大数组:
-
文件大小依赖 SIZE:
- 必须保证 FTP 服务器支持
SIZE命令,否则无法写入正确的file_size,升级头将不可靠。
- 必须保证 FTP 服务器支持
-
错误处理与重试:
- 任意步骤出错,
lwip_ftp_upgrade_step()返回<0; v_ftp_update_action()会清除u8_updata_flag并设置u8_update_result = 1;- 上层可根据日志与结果决定是否重新触发升级。
- 任意步骤出错,
7. 与 4G FTP 的对比
-
4G FTP:
- 文件:
ec200a_4g_ftp.c; - 通过 AT 命令驱动 4G 模块内置 FTP 客户端;
- 状态机在
u8_ec200a_4g_ftp_update()内实现。
- 文件:
-
LwIP FTP(网线):
- 文件:
lwip_module.c+lwip_ftp_upgrade.c; - 使用 LwIP socket 直接实现 FTP 协议;
- 与 4G 共用
flash_upgrade升级区与头部结构; - 集成方式完全嵌入
v_ftp_update_action(),不影响v_Plat_Comm线程的主循环。
- 文件: