Files

9.2 KiB
Raw Permalink Blame History

LwIP FTP 升级功能说明(最终版)

1. 设计目标

  • 不依赖 4G 模块 AT 指令 的情况下,通过 以太网 (LwIP socket) 实现 FTP 固件下载。
  • 复用已有的 flash_upgrade.h 升级区管理:
    • upgrade_erase_program_area() 擦除升级区域;
    • upgrade_write_packet()PACKET_SIZE1024 字节)分包写入;
    • 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_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);

说明:旧版的 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_addrFTP 服务器域名 / IP
  • u16_update_server_portFTP 端口(通常 21);
  • u8_user / u8_pwd:用户名、密码;
  • u8_path:远程目录(可为空);
  • u8_file_name:文件名;
  • u8_netType:网络类型(0=4G1=网线);
  • u32_file_size:文件大小(由 FTP SIZE 结果回填);
  • u8_update_result:升级结果(0=成功,1=失败)。

这些字段由 OCPP UpdateFirmware 命令解析函数 parse_ftp_url() 填充。

3.2 触发流程

  1. OCPP 平台下发 UpdateFirmwareURL 形如:
    ftp://user:pass@host:port/path/file.bin
    → 解析到全局 t_ftp_update_info
  2. 上层把对应平台的 u8_updata_flag 置位。
  3. plat_comm_task.c 中的 v_Plat_Comm() 周期调用 v_ftp_update_action()
  4. v_ftp_update_action() 内部根据:
    • s_bs_task_ctrl.ocpp_data.u8_log_file_upload1=日志上传,0=程序升级
    • t_ftp_update_info.u8_netType0=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 内部定义的状态机大致流程:

  1. LWIP_FTP_STEP_CONNECT
    • 建立 FTP 控制连接(21 端口),并设置为非阻塞。
  2. LWIP_FTP_STEP_WAIT_WELCOME
    • 等待 220 欢迎语。
  3. LWIP_FTP_STEP_SEND_USER / WAIT_USER
    • 发送 USER xxx,等待响应;
    • 若返回 331,继续发送 PASS;否则直接进入 TYPE
  4. LWIP_FTP_STEP_SEND_PASS / WAIT_PASS
    • 发送 PASS xxx,等待登录结果。
  5. LWIP_FTP_STEP_SEND_TYPE / WAIT_TYPE
    • 发送 TYPE I,切换为二进制模式。
  6. LWIP_FTP_STEP_SEND_CWD / WAIT_CWD(可选)
    • 如果 u8_path 非空,发送 CWD <path> 切目录。
  7. LWIP_FTP_STEP_SEND_SIZE / WAIT_SIZE
    • 发送 SIZE <file>,并从 213 响应中解析文件大小;
    • 回填到 s_ctx.file_sizet_ftp_update_info.u32_file_size
  8. LWIP_FTP_STEP_PREPARE_FLASH
    • 调用 upgrade_erase_program_area() 擦除升级区;
    • 构造 upgrade_header_t,写入 UPGRADE_FLAG_IDLE / file_size / packet_count 等信息。
  9. LWIP_FTP_STEP_SEND_PASV / WAIT_PASV
    • 发送 PASV,解析 227 响应得到数据通道 IP/端口。
  10. LWIP_FTP_STEP_CONNECT_DATA
    • data_ip / data_port 建立数据连接 socket。
  11. LWIP_FTP_STEP_SEND_RETR / WAIT_RETR
    • 发送 RETR <file>,等待 150/125 等开始传输响应。
  12. LWIP_FTP_STEP_RECV_DATA
    • 循环接收数据,按 1024 字节分包写入 Flash(见 4.2)。
  13. LWIP_FTP_STEP_SEND_QUIT
    • 发送 QUIT,关闭控制连接;
    • 写入 UPGRADE_FLAG_NEED_UP 头部,标记升级就绪。
  14. LWIP_FTP_STEP_DONE / FAIL
    • DONE:整个流程成功,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)
    • 处理最后不足一包的数据时,也会单独调用一次。
  • 接收进度打印
    • 每次收到数据后,都会根据 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 == 04G)时调用 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] 采用 FreeRTOS pvPortMalloc 动态申请,仅 1KB。
  • 文件大小依赖 SIZE

    • 必须保证 FTP 服务器支持 SIZE 命令,否则无法写入正确的 file_size,升级头将不可靠。
  • 错误处理与重试

    • 任意步骤出错,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 线程的主循环。