概念先知道
- 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/POST 与 HTTPS;本页是让模组“当服务器”。
操作步骤
在终端进入 SDK 的 HTTP RESTful API 例程目录(前提:环境已按快速开始(Linux)或Windows搭好):
cd examples/wifi/sta/http_restful_api例程自带网页数据数组 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统一填写 bl616(例程 defconfig 开启了 CONFIG_HTTPD 等选项):
make CHIP=bl616 BOARD=bl616dk按住 BOOT 键短按 EN/RST 进入下载模式后烧录:
make flash CHIP=bl616 COMX=/dev/ttyUSB0串口助手波特率 2000000,连接路由器后执行 http_test 启动服务器(监听 80 端口):
wifi_sta_connect Your_SSID 12345678
http_test电脑浏览器访问 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 端口,是服务器的入口。
参数:
conn:struct netconnNETCONN_TCP:TCP 类型port:HTTP_SERVER_PORT(80)
返回值:ERR_OK 成功;其他为 lwIP 错误码
netconn_accept / netconn_recv(conn, &newconn) / (conn, &inbuf)
接受客户端连接;读取请求数据到 netbuf。
参数:
conn:监听连接对象newconn:新连接对象(accept 输出)inbuf:netbuf接收缓冲
返回值:ERR_OK 成功
router_match(conn, url, method, body)
(例程自定义)在 httpRouter[] 路由表中查找 (method, url),命中则调用对应 handler,未命中返回 404。
参数:
conn:连接对象url:请求路径method:HtmlRequestMethod_TypeDefbody:请求体(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 完整代码
/****************************************************************************
*
* 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

