Files
P4-USB-transfer/表情包下载流程与文件路径.md
zhengxinandCursor 851e347cf5 初始提交卡包 USB 工程
纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。

Co-authored-by: Cursor <[email protected]>
2026-10-13 21:54:50 +08:00

424 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 表情包下载流程与文件路径文档
## 1. 下载流程概述
表情包下载流程涉及BLE通信、MQTT文件管理、HTTP下载、ZIP解压和文件存储等多个环节。
## 2. 完整下载流程
### 2.1 触发下载(BLE指令)
**位置**: `main/BLE/ble_data_ansy.c`
**流程**:
1. APP通过BLE发送表情包下载指令 `0x000B`(`emoji_pack_download`)
2. 指令格式:`帧头(1) + 类(2) + 指令(2) + Target_pack(1) + CRC16(2) + 帧尾(1) = 9字节`
3. `Target_pack` 为ASCII字符:`'A'`, `'B'`, `'C'`, `'D'` 之一
4. 解析后保存到 `s_emoji_download_target_pack`
5. 设置系统事件 `DOWNLOAD_EMOJI_BIT`
**关键代码**:
```c
// main/BLE/ble_data_ansy.c:398-410
case emoji_pack_download:
char target_pack = (char)input[5]; /* Target_pack: A/B/C/D ASCII */
s_emoji_download_target_pack = target_pack;
ESP_LOGI("BLE_ANSY", "表情包下载 目标系列: %c", target_pack);
set_system_event(DOWNLOAD_EMOJI_BIT);
```
### 2.2 主任务处理下载事件
**位置**: `main/main.c`
**流程**:
1. `main_task` 检测到 `DOWNLOAD_EMOJI_BIT` 事件
2. 检查MQTT连接状态,如未连接则初始化MQTT
3. 等待MQTT连接(最多60秒超时)
4. 调用 `publish_json_download(filename_emoji)` 请求下载URL
**关键代码**:
```c
// main/main.c:212-231
if (event_bits & DOWNLOAD_EMOJI_BIT) {
ESP_LOGI(TAG_MAIN, "处理下载表情事件: %s", filename_emoji);
if (!Get_MQTT_FLAG())
mqtt_main();
// 等待MQTT连接(60秒超时)
while (!Get_MQTT_FLAG()) {
vTaskDelay(pdMS_TO_TICKS(500));
if (超时) break;
}
if (Get_MQTT_FLAG())
publish_json_download(filename_emoji);
clear_system_event(DOWNLOAD_EMOJI_BIT);
}
```
**注意**: `filename_emoji` 需要在触发 `DOWNLOAD_EMOJI_BIT` 之前构建,格式应为 `"icar/A.zip"` 或类似格式(根据 `target_pack` 构建)。
### 2.3 MQTT请求下载URL
**位置**: `main/MQTT/mqtt.c`
**流程**:
1. `publish_json_download(filename)` 构建JSON请求
2. 服务ID: `"$file_manager"`
3. 事件类型: `"get_download_url"`
4. 参数: `file_name` = `filename_emoji`(如 `"icar/A.zip"`)
5. 发布到MQTT主题: `$oc/devices/{device_id}/sys/events/up`
**关键代码**:
```c
// main/MQTT/mqtt.c:181-199
esp_err_t publish_json_download(char *filename) {
cJSON *root = create_data("$file_manager", "get_download_url");
cJSON *paras = cJSON_GetObjectItem(service_object, "paras");
cJSON_AddStringToObject(paras, "file_name", filename);
// 发布到MQTT
esp_mqtt_client_publish(client, mqtt_event_up(object_device_id), ...);
}
```
### 2.4 接收下载URL响应
**位置**: `main/MQTT/mqtt.c`
**流程**:
1. 接收MQTT消息:`$oc/devices/{device_id}/sys/events/down`
2. 解析JSON响应:`event_type = "get_download_url_response"`
3. 提取参数:
- `url`: 下载URL(HTTP/HTTPS)
- `object_name`: 文件名(如 `"icar/A.zip"`)
4. 判断文件类型:
- 如果是 `.bin` 文件 → 触发OTA升级
- 否则 → 调用 `download_file(url, filename)` 下载
**关键代码**:
```c
// main/MQTT/mqtt.c:373-398
else if (strcmp(service_id->valuestring, "$file_manager") == 0) {
if (strcmp(event_type->valuestring, "get_download_url_response") == 0) {
cJSON *url = cJSON_GetObjectItem(paras, "url");
cJSON *object_name = cJSON_GetObjectItem(paras, "object_name");
char *filename = object_name->valuestring;
char *slash = strchr(filename, '/');
if (slash != NULL) slash++; // 提取文件名部分
if (是.bin文件) {
trigger_ota_upgrade(url->valuestring);
} else if (download_file(url_copy, slash) == ESP_OK) {
transmit_download_success(object_name->valuestring);
}
}
}
```
### 2.5 HTTP下载文件
**位置**: `main/FLASH/flash.c`
**流程**:
1. `download_file(firmware_url, filename)` 使用 `esp_http_client` 下载
2. 下载路径: `/extflash/{filename}`(如 `/extflash/A.zip`)
3. 支持断点续传(Range请求)
4. 下载完成后创建解压任务 `extract_task`
**关键代码**:
```c
// main/FLASH/flash.c:383-629
esp_err_t download_file(char *firmware_url, char *filename) {
// 构建完整路径
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, filename);
// base_path = "/extflash"
// HTTP下载
esp_http_client_handle_t client = esp_http_client_init(&config);
// ... 下载数据写入文件 ...
// 下载完成后创建解压任务
xTaskCreateStaticPinnedToCore(extract_task, "extract_task", ...,
(void *)filename, ...);
}
```
**存储位置**:
- **下载ZIP文件路径**: `/extflash/{filename}`(如 `/extflash/A.zip`)
- **文件系统**: FAT文件系统,挂载在外部SPI Flash(`storage_extFlash`分区)
### 2.6 ZIP解压
**位置**: `main/ZIP_EXTRACTOR/zip_extractor.c`
**流程**:
1. `extract_task` 调用 `extract_json_zip(filename)`
2. 构建ZIP文件路径: `/extflash/{filename}`
3. 构建解压目录: `/extflash/{filename去掉.zip后缀}`
- 例如:`/extflash/A.zip` → 解压到 `/extflash/A/`
4. 使用 `miniz` 库解压ZIP文件
5. 解压后的JSON文件直接放在解压目录下(如 `/extflash/A/default.json`)
**关键代码**:
```c
// main/ZIP_EXTRACTOR/zip_extractor.c:533-553
void extract_json_zip(char* filename) {
snprintf(zip_file_path, sizeof(zip_file_path), "%s/%s", "/extflash", filename);
snprintf(extract_directory, sizeof(extract_directory), "%s", zip_file_path);
// 去掉.zip后缀
char *zip_pos = strstr(extract_directory, ".zip");
if (zip_pos) *zip_pos = '\0';
// 解压到目录
extract_zip_file(zip_file_path, extract_directory);
}
// 解压逻辑(main/ZIP_EXTRACTOR/zip_extractor.c:278-491)
void extract_zip_file(const char* zip_path, const char* extract_dir) {
// 遍历ZIP内所有文件
for (mz_uint i = 0; i < num_files; i++) {
// 构建输出路径: extract_dir/文件名
snprintf(output_path, 1024, "%s/%s", extract_dir, file_stat.m_filename);
// 解压文件
mz_zip_reader_extract_to_file(zip_archive, i, output_path, ...);
}
// 解压成功/失败后设置事件
if (成功) {
set_system_event(DOWNLOAD_SUCCESS_BIT);
} else {
set_system_event(DOWNLOAD_FAILED_BIT);
}
}
```
**解压后文件结构**:
```
/extflash/
├── A.zip (下载的ZIP文件)
└── A/ (解压后的目录)
├── default.json
├── calling.json
├── charging.json
└── ... (其他JSON文件)
```
### 2.7 下载成功处理
**位置**: `main/main.c`, `main/BLE/ble.c`
**流程**:
1. `main_task` 检测到 `DOWNLOAD_SUCCESS_BIT` 事件
2. 通知 `notification_task` 发送下载成功反馈(BLE协议帧 `0x0003`)
3. **需要添加**: 调用 `emoji_storage_add_downloaded_pack(pack_id)` 将pack_id添加到已下载列表
**关键代码**:
```c
// main/main.c:233-249
if (event_bits & DOWNLOAD_SUCCESS_BIT) {
ESP_LOGI(TAG_MAIN, "处理下载成功事件");
xTaskNotify(notification_task_handle, BLE_SEND_DL_EMOJI_SUCCESS, eSetBits);
// TODO: 需要添加 emoji_storage_add_downloaded_pack(pack_id)
clear_system_event(DOWNLOAD_SUCCESS_BIT);
}
// main/BLE/ble.c:1221-1231
else if (bits & BLE_SEND_DL_EMOJI_SUCCESS) {
send_emoji_download_result_frame(1); // 发送成功反馈
}
```
## 3. 文件存储路径
### 3.1 下载ZIP文件路径
**格式**: `/extflash/{filename}`
**示例**:
- `/extflash/A.zip`
- `/extflash/B.zip`
- `/extflash/icar/A.zip`(如果filename包含目录)
**文件系统**: FAT文件系统,挂载在外部SPI Flash
### 3.2 解压后文件路径
**格式**: `/extflash/{pack_id}/{json_filename}`
**示例**:
- `/extflash/A/default.json`
- `/extflash/A/calling.json`
- `/extflash/B/charging.json`
**目录结构**:
```
/extflash/
├── A.zip (下载的ZIP文件)
├── A/ (解压后的目录)
│ ├── default.json
│ ├── calling.json
│ └── ...
├── B.zip
├── B/
│ └── ...
└── ...
```
### 3.3 默认表情包路径(SPIFFS)
**格式**: `/spiffs/{json_filename}`
**示例**:
- `/spiffs/default.json`
- `/spiffs/calling.json`
**说明**: 默认表情包存储在SPIFFS分区(`storage`分区),编译时打包到固件中。
## 4. 通过文件路径查找表情包
### 4.1 根据pack_id构建路径
**当前使用的表情包**:
- 通过 `emoji_storage_get_using_pack()` 获取当前pack_id(如 `'A'`, `'B'`, `'C'`, `'D'`)
- 如果pack_id为 `'0'`,使用SPIFFS路径:`/spiffs/{json_filename}`
- 否则使用extflash路径:`/extflash/{pack_id}/{json_filename}`
**路径构建逻辑**(需要实现):
```c
char* get_emoji_path(char pack_id, const char* json_filename) {
static char path[128];
if (pack_id == '0') {
// 默认表情包,使用SPIFFS
snprintf(path, sizeof(path), "/spiffs/%s", json_filename);
} else {
// 下载的表情包,使用extflash
snprintf(path, sizeof(path), "/extflash/%c/%s", pack_id, json_filename);
}
return path;
}
```
### 4.2 文件查找流程
1. **获取当前使用的pack_id**:
```c
char pack_id = emoji_storage_get_using_pack();
```
2. **构建完整路径**:
```c
char* path = get_emoji_path(pack_id, "default.json");
// pack_id='A' → "/extflash/A/default.json"
// pack_id='0' → "/spiffs/default.json"
```
3. **检查文件是否存在**:
```c
struct stat st;
if (stat(path, &st) == 0) {
// 文件存在,可以使用
} else {
// 文件不存在,回退到默认路径
path = "/spiffs/default.json";
}
```
### 4.3 在remapping中使用
**位置**: `main/remapping.c`
**当前实现**: 直接使用 `lottie_files[]` 数组,所有路径都是 `/spiffs/...`
**需要修改**: 根据当前使用的pack_id动态构建路径
**建议修改**:
```c
char* remapping(uint32_t bits) {
char *path = NULL;
char pack_id = emoji_storage_get_using_pack();
switch (bits) {
case emoji_event_start:
if (pack_id == '0') {
path = lottie_files[start_emojo]; // "/spiffs/default.json"
} else {
// 构建路径: "/extflash/{pack_id}/default.json"
snprintf(g_target_path, sizeof(g_target_path),
"/extflash/%c/default.json", pack_id);
path = g_target_path;
}
break;
// ... 其他case类似处理
}
return path;
}
```
## 5. 关键文件位置总结
| 功能 | 文件路径 | 关键函数/变量 |
|------|---------|--------------|
| BLE指令解析 | `main/BLE/ble_data_ansy.c` | `ble_data_ansy()`, `s_emoji_download_target_pack` |
| 主任务处理 | `main/main.c` | `main_task()`, `filename_emoji` |
| MQTT请求 | `main/MQTT/mqtt.c` | `publish_json_download()`, `handle_event()` |
| HTTP下载 | `main/FLASH/flash.c` | `download_file()`, `base_path = "/extflash"` |
| ZIP解压 | `main/ZIP_EXTRACTOR/zip_extractor.c` | `extract_json_zip()`, `extract_zip_file()` |
| 表情包存储 | `main/EMOJI_STORAGE/emoji_storage.c` | `emoji_storage_add_downloaded_pack()`, `emoji_storage_get_using_pack()` |
| 路径映射 | `main/remapping.c` | `remapping()`, `lottie_files[]` |
## 6. 待完善的功能
1. **filename_emoji构建**: 需要在设置 `DOWNLOAD_EMOJI_BIT` 之前构建 `filename_emoji`,格式应为 `"icar/{pack_id}.zip"` 或 `"{pack_id}.zip"`
2. **下载成功后添加pack_id**: 在 `DOWNLOAD_SUCCESS_BIT` 处理中调用 `emoji_storage_add_downloaded_pack(pack_id)` 将pack_id添加到已下载列表
3. **动态路径查找**: 修改 `remapping()` 函数,根据当前使用的pack_id动态构建文件路径,支持从 `/extflash/{pack_id}/` 或 `/spiffs/` 加载JSON文件
4. **文件存在性检查**: 在使用下载的表情包文件前,检查文件是否存在,如果不存在则回退到默认表情包
## 7. 流程图
```
APP发送BLE指令(0x000B, pack_id='A')
↓
ble_data_ansy解析,设置DOWNLOAD_EMOJI_BIT
↓
main_task处理DOWNLOAD_EMOJI_BIT
↓
构建filename_emoji = "icar/A.zip" (或 "A.zip")
↓
publish_json_download(filename_emoji) → MQTT
↓
MQTT响应: get_download_url_response
↓
提取url和object_name
↓
download_file(url, "A.zip") → HTTP下载
↓
保存到: /extflash/A.zip
↓
extract_task解压ZIP
↓
解压到: /extflash/A/
↓
设置DOWNLOAD_SUCCESS_BIT
↓
main_task处理成功事件
↓
emoji_storage_add_downloaded_pack('A')
↓
发送BLE反馈: 下载成功(0x0003)
```
## 8. 注意事项
1. **文件系统挂载**: 确保外部Flash的FAT文件系统已正确挂载到 `/extflash`
2. **路径格式**: 文件名可能包含目录(如 `"icar/A.zip"`),需要正确处理
3. **内存管理**: ZIP解压使用PSRAM,避免栈溢出
4. **错误处理**: 下载失败、解压失败都需要设置相应的事件位并通知APP
5. **pack_id管理**: 确保pack_id('0', 'A', 'B', 'C', 'D')与文件路径一致