由 WT_0213 贡献,Ai-Thinker 进行整理
目前实现上电后自动打开AP模式,连接无线AP后。打开网页提交要连接的WIFI名称 和 WIFI密码 点击提交。相应POST提交的WIFI名称和密码。
以下代码添加了关键点注释,方便大家理解。
发帖积分恢复了,又充满了动力。😄
演示
首先设备上电,上电后在电脑或手机上可以看到如下热点

点击连接

然后会让你输入密码

这里密码是:1234567879
然后通过浏览器输入:
192.168.169.1
可以看到如下界面,简单写了个配置页面效果:

输入要连接的WIFI名称与密码,点击提交。

成功后返回提交的WIFI名和密码;

介绍一下实现方式和思路
打开AP模式
html
static void start_ap(void)
{
wifi_mgmr_ap_params_t config = { 0 };
config.channel = 3;
config.key = USER_AP_PASSWORD;
config.ssid = USER_AP_NAME;
config.use_dhcpd = 1;
if (wifi_mgmr_conf_max_sta(2) != 0) {
return 5;
}
if (wifi_mgmr_ap_start(&config) == 0) {
return 0;
}
}启动http_server
html
void mhttp_server_init()
{
//常用变量
int ss, sc;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int snd_size = 0; /* 发送缓冲区大小 */
socklen_t optlen; /* 选项值长度 */
// int optlen;
int err;
socklen_t addrlen;
// int addrlen;
//建立套接字
ss = socket(AF_INET, SOCK_STREAM, 0);
if (ss < 0)
{
printf("socket error\n");
}
/*设置服务器地址*/
bzero(&server_addr, sizeof(server_addr));
/*清零*/
server_addr.sin_family = AF_INET;
/*协议族*/
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*本地地址*/
server_addr.sin_port = htons(80);
/*服务器端口*/
/*绑定地址结构到套接字描述符*/
err = bind(ss, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (err < 0)
{
printf("bind error\n");
return -1;
}
/*设置侦听*/
err = listen(ss, 7);
if (err < 0)
{
printf("listen error\n");
return -1;
}
addrlen = sizeof(struct sockaddr_in);
MYPARM parm11;
while (1)
{
printf("accept start\r\n");
sc = accept(ss, (struct sockaddr *)&client_addr, &addrlen);
if ((sc < 0) || (mysemaphoreflag > 0))
{
printf("accept fail sc is:%d semaphore is:%d\r\n", sc, mysemaphoreflag);
if (sc > 0)
{
close(sc);
}
continue;
}
parm11.sc = sc;
parm11.buf = NULL;
mysemaphoreflag++;
http_server_thread(&parm11);
vTaskDelay(1);
}
}等待客户端连接即可。为了使设备启动状态更直观,增加了开机亮灯操作。启动后自动打开绿灯。
代码如下:
html
gpio = bflb_device_get_by_name("gpio");
bflb_gpio_init(gpio, GPIO_PIN_14, GPIO_OUTPUT| GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
bflb_gpio_set(gpio, GPIO_PIN_14);主要代码讲解
main.c
c
int main(void)
{
//中开启时钟
board_init();
// 亮绿灯
gpio = bflb_device_get_by_name("gpio");
bflb_gpio_init(gpio, GPIO_PIN_14, GPIO_OUTPUT| GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
bflb_gpio_set(gpio, GPIO_PIN_14);
//设置中断分组
bflb_irq_set_nlbits(4);
//设置中断优先级
bflb_irq_set_priority(37, 3, 0);
bflb_irq_set_priority(WIFI_IRQn, 1, 0);
// 拿到 gpio
gpio = bflb_device_get_by_name("gpio");
// 拿到 uart
uart0 = bflb_device_get_by_name("uart0");
shell_init_with_task(uart0);
// 初始化tcp ip
tcpip_init(NULL, NULL);
wifi_start_firmware_task();
// 创建http服务
create_http_server_task();
vTaskStartScheduler();
while (1) {
}
}创建http服务
html
void create_http_server_task(void)
{
MuxSem_Handle = xSemaphoreCreateMutex();
if (NULL != MuxSem_Handle)
{
printf("MuxSem_Handle creat success!\r\n");
}
xTaskCreate(http_server_task, (char*)"fw", WIFI_HTTP_SERVER_STACK_SIZE, NULL, HTTP_SERVERTASK_PRIORITY, &http_server_task_hd);
}这里使用了xTaskCreate,FreeRTOS的任务。
不了解的可以看下Ai-M61-32S AP 配网学习 之 FreeRTOS任务 https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=43670
接下来是 http_server_task
html
void http_server_task(void* param)
{
// 打开AP
start_ap();
// 启动http服务,也就是响应网页的服务
mhttp_server_init();
}这里比较简单。
打开ap代码
html
static void start_ap(void)
{
wifi_mgmr_ap_params_t config = { 0 };
// 通道
config.channel = 3;
// 设置AP热点名称
config.ssid = USER_AP_NAME;
// 设置密码
config.key = USER_AP_PASSWORD;
// 启动dhcp
config.use_dhcpd = 1;
// 设置最大连接数
if (wifi_mgmr_conf_max_sta(2) != 0) {
return 5;
}
// 启动AP模式
if (wifi_mgmr_ap_start(&config) == 0) {
return 0;
}
}WIFI信息设置
html
#define USER_AP_NAME "Ai-M61-32s"
#define USER_AP_PASSWORD "123456789"然后是http服务的启动
mlwip_https.c
html
void mhttp_server_init()
{
//常用变量
int ss, sc;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int snd_size = 0; /* 发送缓冲区大小 */
socklen_t optlen; /* 选项值长度 */
// int optlen;
int err;
socklen_t addrlen;
// int addrlen;
//建立套接字
ss = socket(AF_INET, SOCK_STREAM, 0);
if (ss < 0)
{
printf("socket error\n");
}
/*设置服务器地址*/
bzero(&server_addr, sizeof(server_addr));
/*清零*/
server_addr.sin_family = AF_INET;
/*协议族*/
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*本地地址*/
server_addr.sin_port = htons(80);
/*服务器端口*/
/*绑定地址结构到套接字描述符*/
err = bind(ss, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (err < 0)
{
printf("bind error\n");
return -1;
}
/*设置侦听*/
err = listen(ss, 7);
if (err < 0)
{
printf("listen error\n");
return -1;
}
addrlen = sizeof(struct sockaddr_in);
MYPARM parm11;
while (1)
{
printf("accept start\r\n");
sc = accept(ss, (struct sockaddr *)&client_addr, &addrlen);
if ((sc < 0) || (mysemaphoreflag > 0))
{
printf("accept fail sc is:%d semaphore is:%d\r\n", sc, mysemaphoreflag);
if (sc > 0)
{
close(sc);
}
continue;
}
parm11.sc = sc;
parm11.buf = NULL;
mysemaphoreflag++;
http_server_thread(&parm11);
vTaskDelay(1);
}
}接下来就是响应请求
📜 点击展开 http_server_thread 完整代码
html
void http_server_thread(void *msg)
{
// printf("http_server_thread\r\n");
MYPARM *parm11;
parm11 = (MYPARM *)msg;
int sc;
char readbuffer[1024];
int size = 0;
char command[1024];
char head_buf[1000];
memset(command, 0, sizeof(command));
memset(head_buf, 0, sizeof(head_buf));
sc = parm11->sc;
memset(readbuffer, 0, sizeof(readbuffer));
while (1)
{
// printf("read stop\r\n");
size = read(sc, readbuffer, 1024);
// int rc = recv(sc, readbuffer, sizeof(readbuffer), 0);
printf("read len:%d\r\n", size);
printf("get:%s\r\n", readbuffer);
if (size <= 0)
{
printf("size <= 0\r\n");
break;
}
int len = get_http_command(readbuffer, command); //得到http 请求中 GET后面的字符串
printf("get:%s len:%d\r\n", command, len);
if (strcmp(command, "/") == 0)
{
printf("command1\r\n");
streatask = 0;
sprintf(head_buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html;charset=UTF-8\r\n\r\n", sizeof(html_page));
// head_buf
// strlen(index_ov2640_html)
// 返回html页面
int ret = write(sc, head_buf, strlen(head_buf));
if (ret == -1)
{
printf("send failed");
close(sc);
mysemaphoreflag--;
return NULL;
}
ret = write(sc, html_page, sizeof(html_page));
if (ret < 0)
{
printf("text write failed");
}
close(sc);
mysemaphoreflag--;
break;
}
else if (strstr(command, "set"))
{
printf("set\r\n");
streatask = 0;
// 获取POST提交过来的数据,拿到ssid部分
char* wifiCfg = strstr(readbuffer, "ssid");
printf("wifiCfg: %s \r\n", wifiCfg);
sprintf(head_buf, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 433\r\nAccess-Control-Allow-Origin: *\r\n\r\n");
int ret = write(sc, head_buf, strlen(head_buf));
if (ret == -1)
{
printf("send failed");
close(sc);
mysemaphoreflag--;
return NULL;
}
// 将post参数切开,分别拿到 ssid 和 pwd
char* ssid = strtok(wifiCfg, "&");
char* pwd = strtok(NULL, "&");
// 将ssid参数切开,分别拿到 key 和 value
char* ssidKey = strtok(ssid, "=");
char* ssidValue = strtok(NULL, "=");
// 将pwd参数切开,分别拿到 key 和 value
char* pwdKey = strtok(pwd, "=");
char* pwdValue = strtok(NULL, "=");
// ============================================
// 待实现 将 wifi 信息写入 存储
// 开启 sta 模式,连接WIFI
// ============================================
printf("OK ssid:%s, pwd:%s \r\n", ssidValue, pwdValue);
// 创建cJSON
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "ssid", ssidValue);
cJSON_AddStringToObject(json, "pwd", pwdValue);
char data_buf[1024];
sprintf(data_buf, "%s\r\n\r\n", cJSON_Print(json));
printf("OK data_buf:%s \r\n", data_buf);
// static char data_buf[] = "{\"ssid\":1,\"pwd\":0}";
// ret = write(sc, data_buf, 433);
// 将获取到的数据通过json形式返回
ret = write(sc, data_buf, sizeof(data_buf));
if (ret < 0)
{
printf("text write failed");
}
close(sc);
mysemaphoreflag--;
break;
}
else
{
streatask = 0;
close(sc);
mysemaphoreflag--;
}
// write(sc,readbuffer,size);
}
//关中断
// free(parm11->buf);
// vTaskDelete(NULL);
//开中断
}html 代码 page.h 很简单的界面,可以做很多优化。
html
static const unsigned char html_page[] = R"(
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WiFi 配置</title>
<style>
body {
font-family: "Microsoft YaHei", Arial, sans-serif;
background: #f5f7fa;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
.card {
background: #fff;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 32px 40px;
width: 320px;
text-align: center;
}
h1 {
font-size: 20px;
color: #333;
margin: 0 0 8px 0;
}
p.tip {
font-size: 13px;
color: #888;
margin: 0 0 24px 0;
}
input[type="text"] {
width: 100%;
box-sizing: border-box;
padding: 10px 12px;
margin-bottom: 14px;
border: 1px solid #d9d9d9;
border-radius: 6px;
font-size: 14px;
outline: none;
}
input[type="text"]:focus {
border-color: #1677ff;
}
input[type="submit"] {
width: 100%;
padding: 10px 0;
border: none;
border-radius: 6px;
background: #1677ff;
color: #fff;
font-size: 15px;
cursor: pointer;
}
input[type="submit"]:hover {
background: #4096ff;
}
</style>
</head>
<body>
<div class="card">
<h1>WiFi 配置</h1>
<p class="tip">请输入路由器名称和密码,点击提交后设备将自动连接。</p>
<form method="post" action="/set">
<input name="ssid" type="text" placeholder="WiFi 名称 (SSID)">
<input name="pwd" type="text" placeholder="WiFi 密码">
<input type="submit" value="提交">
</form>
</div>
</body>
</html>
)";到这里基本,就可以实现 AP 配网的一些前置条件了。
下一步就是将 WIFI信息保存到 存储。
WIFI相关参考文档。
WiFi API指南 — 安信可科技 documentation (wb2-api-web.readthedocs.io)
Wi-Fi Manager — BL IoT SDK release_bl_iot_sdk_1.6.39-238-gf5ba0a7ee 文档 (bouffalolab.github.io)
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

