Contributed by WT_0213, curated by Ai-Thinker
Currently, the device automatically turns on AP mode after power-up. After connecting to the wireless AP, open the web page, submit the WiFi name and WiFi password you want to connect to, and click Submit. It responds to the WiFi name and password submitted via POST.
The following code has comments at key points to make it easier to understand.
My posting points have recovered, and I'm full of motivation again. 😄
Demo
First power on the device. After power-up, you can see the following hotspot on your computer or phone:

Click to connect.

Then it will ask you to enter the password.

The password here is: 1234567879
Then enter the following in the browser:
192.168.169.1
You can see the following interface; I simply wrote a configuration page:

Enter the WiFi name and password to connect to, then click Submit.

On success, it returns the submitted WiFi name and password;

Implementation Approach and Ideas
Turning on AP Mode
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;
}
}Starting the HTTP Server
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);
}
}Just wait for a client to connect. To make the device startup state more intuitive, an LED-on-at-boot operation was added: the green LED turns on automatically after startup.
The code is as follows:
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 Code Explanation
main.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) {
}
}Creating the HTTP Service
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);
}This uses xTaskCreate, the FreeRTOS task API.
If you're not familiar with it, see the FreeRTOS tasks article in the Ai-M61-32S AP provisioning series: https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=43670
Next is http_server_task
void http_server_task(void* param)
{
// 打开AP
start_ap();
// 启动http服务,也就是响应网页的服务
mhttp_server_init();
}This part is simple.
The AP Code
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 Settings
#define USER_AP_NAME "Ai-M61-32s"
#define USER_AP_PASSWORD "123456789"Then the HTTP service starts:
mlwip_https.c
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);
}
}Next, Responding to Requests
📜 Click to expand the full http_server_thread code
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 Code - page.h is a very simple interface; there is plenty of room for optimization.
static const unsigned char html_page[] = R"(
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WiFi Configuration</title>
<style>
body {
font-family: Arial, Helvetica, 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 Configuration</h1>
<p class="tip">Enter your router name and password, then click Submit. The device will connect automatically.</p>
<form method="post" action="/set">
<input name="ssid" type="text" placeholder="WiFi Name (SSID)">
<input name="pwd" type="text" placeholder="WiFi Password">
<input type="submit" value="Submit">
</form>
</div>
</body>
</html>
)";At this point, the prerequisites for AP provisioning are basically implemented.
The next step is to save the WiFi information to storage.
WiFi Reference Documentation
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)
Have questions?
For other questions, please visit the unified discussion area: Ai-Thinker Discussions

