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

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 21:54:50 +08:00

13 KiB
Raw Permalink Blame History

表情包下载流程与文件路径文档

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

关键代码:

// 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

关键代码:

// 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

关键代码:

// 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) 下载

关键代码:

// 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

关键代码:

// 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)

关键代码:

// 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添加到已下载列表

关键代码:

// 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}

路径构建逻辑(需要实现):

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:

    char pack_id = emoji_storage_get_using_pack();
    
  2. 构建完整路径:

    char* path = get_emoji_path(pack_id, "default.json");
    // pack_id='A' → "/extflash/A/default.json"
    // pack_id='0' → "/spiffs/default.json"
    
  3. 检查文件是否存在:

    struct stat st;
    if (stat(path, &st) == 0) {
        // 文件存在,可以使用
    } else {
        // 文件不存在,回退到默认路径
        path = "/spiffs/default.json";
    }
    

4.3 在remapping中使用

位置: main/remapping.c

当前实现: 直接使用 lottie_files[] 数组,所有路径都是 /spiffs/...

需要修改: 根据当前使用的pack_id动态构建路径

建议修改:

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')与文件路径一致