Initial commit: CCU621_M firmware project with BLE debug link support.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
# 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_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_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 触发流程
|
||||
|
||||
1. OCPP 平台下发 `UpdateFirmware`,URL 形如:
|
||||
`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_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()` 中流程为:
|
||||
|
||||
```c
|
||||
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_size` 和 `t_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` 计算百分比:
|
||||
```c
|
||||
Plat_Comm_LOG("lwip ftp: recv %lu/%lu (%u%%)\r\n", ...);
|
||||
```
|
||||
- 方便在串口日志中实时观察下载进度。
|
||||
|
||||
### 4.3 控制通道响应等待(最长 1 秒)
|
||||
|
||||
- 所有 FTP 控制命令的响应(`220 / 331 / 230 / 213 / 227 / 150 ...`)统一通过:
|
||||
|
||||
```c
|
||||
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]` 采用 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` 线程的主循环。
|
||||
|
||||
Reference in New Issue
Block a user