Skip to content

概念先知道

  • RESTful API:把“资源”和“操作”用 HTTP 方法表达:GET 查询、POST 创建、PUT/PATCH 更新、DELETE 删除。返回内容通常是 JSON。
  • HTTP 服务器:模组作为服务器监听端口,等待电脑/手机/云端来访问,适合“设备被远程控制”的场景。
  • 路由(Router):例程维护一张路由表,把 URL 与方法映射到处理函数;未匹配的路径返回 404。
  • lwIP netconn API:例程用 netconn_new/bind/listen/accept/recv/write 实现多线程 HTTP 服务,是 lwIP 的 socket 之上的高级接口。

例程功能简介

本页对应博流官方 SDK 的 http_restful_api 例程(examples/wifi/sta/http_restful_api):

  • http_test 命令启动 HTTP 服务器(http_server_restfulAPI.c),监听 80 端口;
  • 路由表如下(摘自 httpRouter[]):
方法URL作用
GET/api/v1/gpios查询所有 GPIO 状态
POST/api/v1/gpios创建/设置一个 GPIO(请求体 JSON)
PUT/api/v1/gpios/更新指定 GPIO(ID 在 URL 里)
DELETE/api/v1/gpios/删除指定 GPIO
PATCH/api/v1/gpios/局部更新指定 GPIO
  • 同时提供网页(fsdata_custom.c)便于在浏览器里操作;
  • main.c 负责 Wi-Fi/shell/事件初始化,服务器逻辑在 http_server_restfulAPI.c
  • 同族参考:客户端请求见 HTTP GET/POSTHTTPS;本页是让模组“当服务器”。

操作步骤

1
进入例程目录

在终端进入 SDK 的 HTTP RESTful API 例程目录(前提:环境已按快速开始(Linux)Windows搭好):

cd examples/wifi/sta/http_restful_api
2
准备网页资源(可选)

例程自带网页数据数组 fsdata_custom.c(控制页)。若修改了网页,用 makefsdata 目录里的工具重新生成并覆盖到 components/net/lwip/lwip/src/apps/http/fsdata.c,再重新编译:

cd makefsdata
cp fsdata.c ../../../components/net/lwip/lwip/src/apps/http/fsdata.c
3
编译工程

统一填写 bl616(例程 defconfig 开启了 CONFIG_HTTPD 等选项):

make CHIP=bl616 BOARD=bl616dk
4
烧录固件

按住 BOOT 键短按 EN/RST 进入下载模式后烧录:

make flash CHIP=bl616 COMX=/dev/ttyUSB0
5
连接 Wi-Fi 并启动 HTTP 服务器

串口助手波特率 2000000,连接路由器后执行 http_test 启动服务器(监听 80 端口):

wifi_sta_connect Your_SSID 12345678
http_test
6
运行验证

电脑浏览器访问 http://<模组IP>/ 打开控制页;或用 curl 调用 RESTful 接口,例如查询 GPIO 列表、创建/修改/删除 GPIO:

curl http://192.168.1.3/api/v1/gpios
curl -X POST http://192.168.1.3/api/v1/gpios -d '{"pin":3,"value":1}'
curl -X DELETE http://192.168.1.3/api/v1/gpios/3

代码执行流程

RESTful 服务器从启动到处理请求的完整流程如下:

例程调用的 API 介绍

netconn_new / netconn_bind / netconn_listen(NETCONN_TCP) / (conn, addr, port) / (conn)

创建 TCP 连接对象并监听 80 端口,是服务器的入口。

参数

  • connstruct netconn
  • NETCONN_TCP:TCP 类型
  • portHTTP_SERVER_PORT(80)

返回值ERR_OK 成功;其他为 lwIP 错误码

netconn_accept / netconn_recv(conn, &newconn) / (conn, &inbuf)

接受客户端连接;读取请求数据到 netbuf

参数

  • conn:监听连接对象
  • newconn:新连接对象(accept 输出)
  • inbufnetbuf 接收缓冲

返回值ERR_OK 成功

router_match(conn, url, method, body)

(例程自定义)在 httpRouter[] 路由表中查找 (method, url),命中则调用对应 handler,未命中返回 404。

参数

  • conn:连接对象
  • url:请求路径
  • methodHtmlRequestMethod_TypeDef
  • body:请求体(JSON)

返回值:无

netconn_write(conn, data, len, NETCONN_COPY)

向客户端发送响应(状态行 + JSON body)。

参数

  • conn:连接对象
  • data / len:响应数据与长度
  • NETCONN_COPY:复制发送标志

返回值ERR_OK 成功

完整代码

以下为 main.c 完整源码,与官方示例(examples/wifi/sta/http_restful_api)一致(服务器路由逻辑在同目录 http_server_restfulAPI.c),默认折叠,点击展开:

📜 点击展开 http_restful_api/main.c 完整代码
c
/****************************************************************************
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

#include <lwip/tcpip.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>

#include "wifi_mgmr_ext.h"

#include "bflb_irq.h"
#include "bflb_uart.h"

#include "rfparam_adapter.h"

#include "board.h"
#include "shell.h"

#define DBG_TAG "MAIN"
#include "log.h"
#include "async_event.h"

struct bflb_device_s *gpio;

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct bflb_device_s *uart0;

static wifi_conf_t conf = {
    .country_code = "CN",
};

extern void shell_init_with_task(struct bflb_device_s *shell);
extern void wifi_event_handler(async_input_event_t ev, void *priv);

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/****************************************************************************
 * Functions
 ****************************************************************************/

void wifi_start_firmware_task(void *param)
{
    LOG_I("Starting wifi ...\r\n");

    async_register_event_filter(EV_WIFI, wifi_event_handler, NULL);


    wifi_task_create();

    LOG_I("Starting fhost ...\r\n");
    fhost_init();

    vTaskDelete(NULL);
}

void wifi_event_handler(async_input_event_t ev, void *priv)
{
    uint32_t code = ev->code;

    switch (code) {
        case CODE_WIFI_ON_INIT_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_INIT_DONE\r\n", __func__);
            wifi_mgmr_init(&conf);
        } break;
        case CODE_WIFI_ON_MGMR_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_MGMR_DONE\r\n", __func__);
        } break;
        case CODE_WIFI_ON_SCAN_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_SCAN_DONE\r\n", __func__);
            wifi_mgmr_sta_scanlist();
        } break;
        case CODE_WIFI_ON_CONNECTED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_CONNECTED\r\n", __func__);
            void mm_sec_keydump();
            mm_sec_keydump();
        } break;
        #ifdef CODE_WIFI_ON_GOT_IP_ABORT
        case CODE_WIFI_ON_GOT_IP_ABORT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP_ABORT\r\n", __func__);
        } break;
        #endif
        #ifdef CODE_WIFI_ON_GOT_IP_TIMEOUT
        case CODE_WIFI_ON_GOT_IP_TIMEOUT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP_TIMEOUT\r\n", __func__);
        } break;
        #endif
        case CODE_WIFI_ON_GOT_IP: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP\r\n", __func__);
            LOG_I("[SYS] Memory left is %d Bytes\r\n", kfree_size(0));
        } break;
        case CODE_WIFI_ON_DISCONNECT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_DISCONNECT\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STARTED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STARTED\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STOPPED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STOPPED\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STA_ADD: {
            LOG_I("[APP] [EVT] [AP] [ADD] %lld\r\n", xTaskGetTickCount());
        } break;
        case CODE_WIFI_ON_AP_STA_DEL: {
            LOG_I("[APP] [EVT] [AP] [DEL] %lld\r\n", xTaskGetTickCount());
        } break;
        default: {
            LOG_I("[APP] [EVT] Unknown code %u \r\n", code);
        }
    }
}

int main(void)
{
    board_init();

    uart0 = bflb_device_get_by_name("uart0");
    shell_init_with_task(uart0);

    if (0 != rfparam_init(0, NULL, 0)) {
        LOG_I("PHY RF init failed!\r\n");
        return 0;
    }

    LOG_I("PHY RF init success!\r\n");

    tcpip_init(NULL, NULL);

    xTaskCreate(wifi_start_firmware_task, "wifi init", 1024, NULL, 10, NULL);

    vTaskStartScheduler();

    while (1) {
    }
}

FAQ

浏览器打不开模组的网页

确认模组已拿到 IP(浏览器地址要写模组的 IP),且先执行了 http_test;电脑与模组同一网段;路由器开启“AP 隔离”时设备间无法互访,需关闭。

curl 请求返回 404

检查 URL 与路由表是否一致(注意 /api/v1/gpios/api/v1/gpios/ 结尾斜杠的区别,PUT/DELETE 使用带斜杠的路径);请求方法(GET/POST/PUT/DELETE)也要匹配路由表。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23