网络访问与文件下载

本文说明本章节功能的接入方式、关键源码和应用参考,帮助开发者快速完成集成。

1. 应用例程

场景

文件

调用接口

小智机器人联网对话入口

app_chatbot_gui.c

网络数据回调、app_malloc() / app_free()

AI App 火山方舟 Chat Completions

ai_main.c

webclient_session_create()webclient_post()webclient_read()

AI App 火山同声传译 WebSocket

ai_simi_service.c

WebSocket 连接、音频数据收发回调

浏览器 HTTP 图片 / 视频 / JSON 请求

browser_thread.c

webclient_session_create()webclient_get()webclient_read()

云书城列表 / 封面 / 下载

reader_cloud.c

webclient_session_create()webclient_get()webclient_read()

高德地图网络请求

gaode_map_net_process.c

高德地图网络请求封装接口

高德地图离线包下载

gaode_map_svg_download.c

webclient_session_create()webclient_get()webclient_read()

Wi-Fi P2P / 投屏入口

panel_cast_ui.c

Wi-Fi P2P / 投屏入口回调

2. 简介

本文中的“网络访问”指设备通过 Wi-Fi、4G/LTE 或 BT PAN 获得 IP 网络能力后,由固件业务代码直接访问服务器。例如:

  • 获取天气数据;

  • 同步账号、设备配置或业务数据;

  • 下载电子书、图片、资源包、音频文件;

  • 通过 HTTP/HTTPS、WebSocket 或 MQTT 访问云服务;

  • 访问大模型服务,获取文本、语音或流式响应。

业务开发时建议把网络访问拆成四层理解:

层级

作用

常见选择

说明

网络承载

让设备获得 IP 网络能力

Wi-Fi、4G/LTE、BT PAN

业务代码通常只关心网络是否 ready

网络状态

判断当前是否可以访问服务器

net_is_connected()

访问服务器前必须先判断

应用协议

与服务器交换数据

HTTP/HTTPS、WebSocket、MQTT、socket

HTTP 下载文件,WebSocket/MQTT 适合长连接和 AI 交互

数据落点

服务器返回数据保存到哪里

内存、文件系统、ringbuffer、业务 parser

小文件可放内存,大文件建议放文件系统,流式音频建议放 ringbuffer

本文不描述手机 App 下载后再通过蓝牙发送给设备的文件传输流程。该类流程属于手机协同传输或文件传输业务,不属于设备直接访问网络。

3. Solution 提供的访问网络方式

3.1 Wi-Fi

Wi-Fi 适合设备直接连接路由器访问服务器。当前 Solution 中 Wi-Fi 主要通过 SDIO Wi-Fi 模块接入,应用侧可使用 app_wifi_open()app_wifi_sta_open()app_wifi_scan()app_wifi_connect()。底层模组、menuconfig 和调试命令请参考 SDIO Wi-Fi

3.2 4G/LTE

4G/LTE 适合设备独立联网,不依赖手机或路由器。当前应用示例通过 app_net_init()NET_OP_LTE_BEGIN 发起 LTE 入网。模组接入、AT 命令、PPP/PPPoS 或 netdev/lwIP 适配请参考 4G(LTE)

3.3 BT PAN

BT PAN 是通过蓝牙 PAN profile 获得 IP 网络能力。普通 BT 配对或 A2DP/HFP 连接不等于可以上网,必须启用 PAN profile 并完成 lwIP 网络适配。

完成 PAN 入网后,业务层看到的是一个 IP 网络通道,可以使用 HTTP/HTTPS、socket、WebSocket 等协议访问服务器。典型场景包括获取天气数据、同步账号或设备配置、下载电子书和图片资源、获取语音或音乐资源,也可以用于 HTTP OTA。OTA 只是 PAN 的一个应用场景,不代表 PAN 只能用于 OTA。

4. 配置 Solution 实现访问网络

配置网络访问时,先选择设备使用哪一种网络承载,再配置对应承载。Wi-Fi、4G/LTE、BT PAN 都可以让设备获得 IP 网络能力,项目中可以只选一种,也可以同时支持多种;业务代码最终都应统一等待网络 ready 后再访问服务器。

建议按下面顺序处理:

步骤

配置内容

说明

1

选择网络承载

按产品形态选择 Wi-Fi、4G/LTE、BT PAN 中的一种或多种

2

打开网络框架

路径为 menuconfig (Top) Components Config Using network (APP_NETWORK_USED);该项仅在 BT_FINSH_PANAPP_WIFI_USEDBSP_USING_PC_SIMULATOR 任一条件满足时可见,默认开启。4G/LTE 仅会启用 modem 和 lwIP,不会单独使该选项显示

3

配置具体承载

Wi-Fi 配置模组和驱动,4G 配置 modem 和数据通道,BT PAN 配置 PAN profile 和 lwIP 适配

4

业务代码等待网络 ready

访问服务器前等待 net_is_connected() 返回 true

4.1 Wi-Fi 配置入口

应用层 Wi-Fi 开关为 APP_WIFI_USED。以包含该配置项的产品工程为例,路径为:menuconfig (Top) Product Applicaiton Config Enanle application/setting/setting_wifi (APP_WIFI_USED)。该开关会自动选择 RT_USING_WIFIRT_USING_LWIP;当前 grid_view 示例默认开启,其他产品工程应以其自身 Kconfig 默认值为准。APP_NETWORK_USED 依赖 APP_WIFI_USED,因此会随该承载条件变为可用;这些均是 Kconfig 配置符号,不应手工在配置头或源码中定义同名宏。

SDIO/SDHCI 控制器的路径为:menuconfig (Top) SiFli SDK configuration Board Config SDIO (BSP_USING_SDIO)BSP_USING_SDIO 直接依赖 BF0_HCPU,且其父级板级驱动菜单不在 PC Simulator 工程显示,因此仅在非 PC Simulator 的 HCPU 工程显示;启用后自动选择 RT_USING_SDIO。再按硬件选择 SDMMC1SDMMC2,并将对应接口模式设为 Wi-Fi 所需的 SDIO 模式。Wi-Fi 模组开关的路径为:menuconfig (Top) SiFli SDK configuration Board Config Board Peripherals,在其中选择对应模组。SWT6621SL 使用 Enable swt 6621 wifi mode (BSP_WIFI_SWT6621);AIC8800MC 依次打开 WIFI mod aic8800mc enable (WIFI_USING_AIC8800MC)Enable using aicxtek WIFI (USING_AICXTEK_WIFI),并在 hardware wifi interface type 中选择 SDIO interface sdio (WIFI_INF_TYPE_SDIO)

具体模组配置请参考 SDIO Wi-Fi - menuconfig 配置。如果 Wi-Fi 已编译但无法扫描或连接,优先回到 SDIO Wi-Fi 检查 SDIO 时钟、pinmux、电源控制和固件路径。

应用代码中常用接口为:

app_wifi_open();
app_wifi_sta_open();
app_wifi_scan();
app_wifi_connect(ssid, password, NULL);

4.2 4G/LTE 配置入口

4G/LTE 的总开关路径为:menuconfig (Top) Components Config Using 4G modem module (USING_MODEM_SUPPORT)。该开关无直接 depends on,默认关闭;启用后自动选择 BSP_USING_MODEMPERI_USING_ONCHIPRT_USING_LWIPAPP_NETWORK_USED 不依赖 LTE/modem;如业务需要使用该网络状态框架,须另行满足 BT_FINSH_PANAPP_WIFI_USEDBSP_USING_PC_SIMULATOR 中的任一条件。

剩下只按实际 modem 配置 UART/AT、SIM/APN 和数据通道。如果使用 PPP/PPPoS,再打开对应 PPP 组件。完整的 lwIP、PPP 和 PPPoS 路径请参考 4G(LTE)- 配置项

应用代码中常用接口为:

app_net_init();
send_msg_to_net_thread(NET_OP_LTE_BEGIN, NULL, 0);

4.3 BT PAN 配置入口

BT PAN 配置需要先打开 BT 基础功能和 lwIP,再打开 PAN profile。配置时按以下顺序确认:

  1. 在 HCPU 工程的 menuconfig (Top) Bluetooth config Enable bluetooth (BLUETOOTH) 启用 BLUETOOTH;再在同一菜单启用 Bluetooth service (BSP_BLE_SIBLES)Enable bt peripherals device (PERI_USING_BT) 与其子项 Enable BT finsh (BT_FINSH)BSP_BLE_SIBLES 在 LCPU 上仅支持 55x;BT_FINSH 依赖 BSP_BLE_SIBLES 且不支持 55x。

  2. menuconfig (Top) SiFli SDK configuration RTOS RT Thread RT-Thread Components Network LwIP: light weight TCP/IP stack (RT_USING_LWIP) 启用 RT_USING_LWIP。该项默认关闭,启用后自动选择 RT_USING_DEVICERT_USING_DEVICE_IPC,并在启用 SAL 时选择 SAL_USING_LWIP

  3. menuconfig (Top) SiFli SDK configuration SiFli Built-in Components Bluetooth Classic BT service Enable BT pan finsh (BT_FINSH_PAN) 启用 PAN。BT_FINSH_PAN 依赖 BT_FINSHRT_USING_LWIP,默认关闭,因此任一前置项未启用时不会显示。

  4. 如果使用手动经典 BT profile,在 menuconfig (Top) Bluetooth config Enable bluetooth Manually select profiles (BT_PROFILE_CUSTOMIZE) Enable PAN (CFG_PAN) 启用 CFG_PANCFG_PAN 依赖 BT_PROFILE_CUSTOMIZE,默认关闭,并会自动选择 BT_FINSH_PAN;仍须满足前述 BT_FINSHRT_USING_LWIP 依赖。

PAN 配置完成后,上层业务代码不应关心底层网络承载是蓝牙、Wi-Fi 还是 4G,而应统一等待网络 ready,然后使用 HTTP/HTTPS、socket、WebSocket 等协议访问服务器。

5. 通用网络状态判断

业务代码不要在收到 Wi-Fi、LTE 或 BT PAN 连接事件后立即访问服务器,而应统一等待 net_is_connected()。推荐和 solution/examples/_app_demo/application/connectivity/demo_connectivity.c 中的使用方式保持一致。

#include <rtthread.h>

#if defined(APP_NETWORK_USED)
    #include "app_net.h"
#endif

static int demo_wait_network_ready(uint32_t timeout_ms)
{
#if defined(APP_NETWORK_USED)
    uint32_t elapsed = 0;

    while (elapsed < timeout_ms)
    {
        if (net_is_connected())
        {
            return 0;
        }

        rt_thread_mdelay(200);
        elapsed += 200;
    }

    return -1;
#else
    rt_kprintf("APP_NETWORK_USED is not enabled\n");
    return -1;
#endif
}

6. 不同承载的联网流程

不同承载的差异只存在于“如何入网”。业务代码在访问服务器前统一等待 net_is_connected()。示例工程可参考 solution/examples/_app_demo/application/connectivity/demo_connectivity.c 中的 conn_network_ready()wifi_join_event_cb()lte_connect_event_cb()ping_event_cb()

6.1 BT PAN

BT PAN 由 profile 负责连接和建立 IP 网络通道,上层只等待 net_is_connected()conn_network_ready()ping_event_cb() 对 BT PAN 同样适用:

int demo_bt_pan_wait_ready(void)
{
    if (demo_wait_network_ready(15000) != 0)
    {
        rt_kprintf("BT PAN connect timeout\n");
        return -1;
    }

    rt_kprintf("BT PAN connected\n");
    return 0;
}

6.2 Wi-Fi

连接 AP 时,先发起 Wi-Fi 连接,再等待网络 ready:

#include <rtthread.h>

#if defined(APP_NETWORK_USED)
    #include "app_net.h"
#endif

#if defined(APP_WIFI_USED)
    #include "app_wifi.h"
#endif

#define DEMO_WIFI_SSID      "your_ssid"
#define DEMO_WIFI_PASSWORD  "your_password"

int demo_wifi_connect(void)
{
#if defined(APP_WIFI_USED)
    app_wifi_open();
    app_wifi_sta_open();
    app_wifi_connect(DEMO_WIFI_SSID, DEMO_WIFI_PASSWORD, NULL);

    if (demo_wait_network_ready(15000) != 0)
    {
        rt_kprintf("Wi-Fi connect timeout\n");
        return -1;
    }

    rt_kprintf("Wi-Fi connected\n");
    return 0;
#else
    rt_kprintf("APP_WIFI_USED is not enabled\n");
    return -1;
#endif
}

如果只需要扫描 AP,可以使用:

void demo_wifi_scan(void)
{
#if defined(APP_WIFI_USED)
    app_wifi_open();
    app_wifi_sta_open();
    app_wifi_scan();
#else
    rt_kprintf("APP_WIFI_USED is not enabled\n");
#endif
}

6.3 4G/LTE

4G/LTE 入网后同样等待网络 ready:

#include <rtthread.h>

#if defined(APP_NETWORK_USED)
    #include "app_net.h"
#endif

int demo_lte_connect(void)
{
#if defined(APP_NETWORK_USED)
    app_net_init();
    send_msg_to_net_thread(NET_OP_LTE_BEGIN, NULL, 0);

    if (demo_wait_network_ready(30000) != 0)
    {
        rt_kprintf("LTE connect timeout\n");
        return -1;
    }

    rt_kprintf("LTE connected\n");
    return 0;
#else
    rt_kprintf("APP_NETWORK_USED is not enabled\n");
    return -1;
#endif
}

7. 网络连通性和访问测试

7.1 Ping 连通性测试

如果工程打开了 ping 支持,可以在网络 ready 后测试基础网络连通性。调用方式和 connectivity demo 一致。

#include <rtthread.h>

#if defined(APP_NETWORK_USED)
    #include "app_net.h"
#endif

#if defined(RT_LWIP_USING_PING) && (defined(PKG_NETUTILS_PING) || !defined(RT_USING_NETDEV))
    extern rt_err_t ping(char *target_name, rt_uint32_t times, rt_size_t size);
#endif

void demo_ping(void)
{
#if defined(APP_NETWORK_USED)
    app_net_init();

    if (!net_is_connected())
    {
        rt_kprintf("network is not connected\n");
        return;
    }

#if defined(RT_LWIP_USING_PING) && (defined(PKG_NETUTILS_PING) || !defined(RT_USING_NETDEV))
    ping("8.8.8.8", 1, 0);
#else
    rt_kprintf("RT_LWIP_USING_PING is not enabled\n");
#endif
#endif
}

7.2 HTTP 下载访问测试

访问测试建议直接下载一个已知大小的 HTTP 文件,循环 webclient_read() 并统计总字节数和耗时。下载前仍然要确认 net_is_connected()。Solution 框架中的网络临时缓存建议使用 app_cache_alloc() / app_cache_free()

#include <rtthread.h>
#include <webclient.h>
#include "app_mem.h"

#if defined(APP_NETWORK_USED)
    #include "app_net.h"
#endif

#define DEMO_SPEED_HEADER_SIZE  1024
#define DEMO_SPEED_READ_SIZE    4096

int demo_http_speed_test(const char *uri)
{
#if defined(APP_NETWORK_USED)
    struct webclient_session *session = RT_NULL;
    uint8_t *read_buf = RT_NULL;
    rt_tick_t start_tick;
    rt_tick_t elapsed_tick;
    rt_uint32_t total_bytes = 0;
    rt_uint32_t kb_s;
    int resp_status;
    int bytes_read;
    int ret = -1;

    if (!net_is_connected())
    {
        rt_kprintf("network is not connected\n");
        return -1;
    }

    session = webclient_session_create(DEMO_SPEED_HEADER_SIZE);
    if (!session)
    {
        goto exit;
    }

    read_buf = app_cache_alloc(DEMO_SPEED_READ_SIZE, CACHE_PSRAM);
    if (!read_buf)
    {
        goto exit;
    }

    resp_status = webclient_get(session, uri);
    if (resp_status != 200)
    {
        rt_kprintf("webclient_get failed, status=%d\n", resp_status);
        goto exit;
    }

    start_tick = rt_tick_get();
    while (1)
    {
        bytes_read = webclient_read(session, read_buf, DEMO_SPEED_READ_SIZE);
        if (bytes_read < 0)
        {
            goto exit;
        }
        if (bytes_read == 0)
        {
            break;
        }

        total_bytes += bytes_read;
    }

    elapsed_tick = rt_tick_get() - start_tick;
    if (elapsed_tick == 0)
    {
        elapsed_tick = 1;
    }

    kb_s = (rt_uint32_t)(((rt_uint64_t)total_bytes * RT_TICK_PER_SECOND) / elapsed_tick / 1024);
    rt_kprintf("download %u bytes, speed %u KB/s, %u.%u Mbps\n",
               total_bytes, kb_s, kb_s * 8 / 1024, (kb_s * 8 % 1024) * 10 / 1024);
    ret = 0;

exit:
    if (read_buf)
    {
        app_cache_free(read_buf);
    }
    if (session)
    {
        webclient_close(session);
    }

    return ret;
#else
    rt_kprintf("APP_NETWORK_USED is not enabled\n");
    return -1;
#endif
}

使用时传入服务器上的测试文件地址:

demo_http_speed_test("http://your_server/test_10m.bin");

connectivity demo 中的 speed 页面偏 UI 展示参考,实际项目中建议以上层 HTTP 下载统计为准。

8. HTTP 下载文件到内存

HTTP/HTTPS 可使用 WebClient 访问。WebClient 的接口文档见 RT-Thread WebClient 说明。下载到内存适合 JSON、小图片、短音频等小文件。Solution 框架中避免把下载结果放到普通栈或长时间占用 system heap,建议使用 app_cache_alloc() / app_cache_realloc() / app_cache_free() 管理数据,减少通用 system heap 压力。

#include <rtthread.h>
#include <webclient.h>
#include <string.h>
#include "app_mem.h"

#define DEMO_HTTP_HEADER_SIZE  1024
#define DEMO_HTTP_READ_SIZE    1024

typedef struct
{
    uint8_t *data;
    uint32_t size;
    uint32_t capacity;
} demo_mem_file_t;

static int demo_mem_file_append(demo_mem_file_t *file, const uint8_t *data, uint32_t len)
{
    uint8_t *new_data;
    uint32_t new_capacity;

    if (!file || !data || len == 0)
    {
        return -1;
    }

    if (file->size + len > file->capacity)
    {
        new_capacity = file->capacity ? file->capacity * 2 : 4096;
        while (new_capacity < file->size + len)
        {
            new_capacity *= 2;
        }

        if (file->data)
        {
            new_data = app_cache_realloc(file->data, new_capacity);
        }
        else
        {
            new_data = app_cache_alloc(new_capacity, CACHE_PSRAM);
        }

        if (!new_data)
        {
            return -1;
        }

        file->data = new_data;
        file->capacity = new_capacity;
    }

    memcpy(file->data + file->size, data, len);
    file->size += len;
    return 0;
}

int demo_http_get_to_memory(const char *uri, demo_mem_file_t *out_file)
{
    struct webclient_session *session = RT_NULL;
    uint8_t *read_buf = RT_NULL;
    int resp_status;
    int bytes_read;
    int ret = -1;

    if (!uri || !out_file)
    {
        return -1;
    }

    memset(out_file, 0, sizeof(*out_file));

    session = webclient_session_create(DEMO_HTTP_HEADER_SIZE);
    if (!session)
    {
        goto exit;
    }

    resp_status = webclient_get(session, uri);
    if (resp_status != 200)
    {
        rt_kprintf("webclient_get failed, status=%d\n", resp_status);
        goto exit;
    }

    read_buf = app_cache_alloc(DEMO_HTTP_READ_SIZE, CACHE_PSRAM);
    if (!read_buf)
    {
        goto exit;
    }

    while (1)
    {
        bytes_read = webclient_read(session, read_buf, DEMO_HTTP_READ_SIZE);
        if (bytes_read < 0)
        {
            goto exit;
        }
        if (bytes_read == 0)
        {
            break;
        }
        if (demo_mem_file_append(out_file, read_buf, bytes_read) != 0)
        {
            goto exit;
        }
    }

    ret = 0;

exit:
    if (ret != 0 && out_file->data)
    {
        app_cache_free(out_file->data);
        memset(out_file, 0, sizeof(*out_file));
    }
    if (read_buf)
    {
        app_cache_free(read_buf);
    }
    if (session)
    {
        webclient_close(session);
    }

    return ret;
}

使用示例:

demo_mem_file_t file;

if (demo_http_get_to_memory("http://example.com/test.bin", &file) == 0)
{
    rt_kprintf("download ok, size=%u\n", file.size);

    /* file.data / file.size 交给业务处理 */
    app_cache_free(file.data);
}

9. HTTP 下载文件到文件系统

WebClient 提供 webclient_get_file(),适合大文件、资源包、音频文件等下载到文件系统的场景。使用前应确认文件系统已挂载、路径可写且空间足够。

#include <rtthread.h>
#include <webclient.h>

int demo_http_get_to_file(const char *uri, const char *path)
{
    int ret;

    if (!uri || !path)
    {
        return -1;
    }

    ret = webclient_get_file(uri, path);
    if (ret != 0)
    {
        rt_kprintf("download file failed, ret=%d\n", ret);
        return -1;
    }

    rt_kprintf("download file ok: %s\n", path);
    return 0;
}

如果业务层需要进度、断点续传或自定义校验,可以参考 webclient_read() 循环读取后自行写入文件。

10. WebSocket/MQTT 和大模型访问

访问大模型通常有两类方式:

  1. 使用现有的小智 AI 组件;

  2. 自行使用 HTTPS/WebSocket/MQTT 对接第三方或私有化大模型服务。

10.1 使用小智 AI 组件

小智 AI 组件位于:

solution/components/xiaozhi/

组件内已经封装网络连接、WebSocket/MQTT 通信、TTS/STT/LLM 事件处理以及 Opus 音频链路。使用前需要确认:

XIAOZHI_SUPPORT
APP_NETWORK_USED
WebSocket / MQTT 相关组件

小智组件中的服务器参数示例:

#define XIAOZHI_HOST    "api.tenclass.net"
#define XIAOZHI_WSPATH  "/xiaozhi/v1/"
#define XIAOZHI_TOKEN   "Bearer 12345678"

音频参数示例:

{
    "format": "opus",
    "sample_rate": 16000,
    "channels": 1,
    "frame_duration": 60
}

10.2 自定义访问大模型 HTTP API

如果客户使用 OpenAI、DeepSeek、通义、豆包或私有化模型,推荐流程如下:

业务线程等待 net_is_connected()
创建 HTTPS session
设置 Authorization 和 Content-Type 头
POST JSON 请求
读取 JSON 或流式响应
文本交给 UI,音频交给音频播放链路

WebClient 的 POST 使用方式可参考 WebClient 示例;动态应用中的 AI App 也可作为接入火山 Chat Completions 和火山同传翻译 WebSocket 的参考。服务器返回流式音频时,播放方式请参考 网络音频播放

11. HTTPS 证书管理

动态证书回退机制的完整配置路径为:menuconfig (Top) SiFli SDK configuration Third party packages Seleted MBedTLS (PKG_USING_MBEDTLS) Enable dynamic cert fallback for mbedtls_client_connect() (PKG_USING_MBEDTLS_TLS_CLIENT_CONNECT_FALLBACK_ALIAS)。先启用 PKG_USING_MBEDTLS,动态证书回退选项才会显示;启用后,mbedtls_client_connect() 可使用动态证书回退逻辑。

12. 与其他文档的分工

  • 本文说明应用层如何联网、访问服务器和下载文件。

  • Wi-Fi 模组、固件和 menuconfig 细节见 SDIO Wi-Fi

  • 4G modem、AT 命令和 PPP/netdev 细节见 4G(LTE)

  • BT PAN 的 profile 配置见 蓝牙

  • HTTP OTA 的固件升级流程见 网络 OTA

  • 网络音频播放、格式转换和息屏播放见 网络音频播放

  • 网络问题排查见 网络 FAQAudio FAQ