本帖最后由 WT_0213 于 2023-12-6 10:09 编辑
目前实现上电后自动打开AP模式,连接无线AP后。打开网页提交要连接的WIFI名称 和 WIFI密码 点击提交。相应POST提交的WIFI名称和密码。
以下代码添加了关键点注释,方便大家理解。
发帖积分恢复了,又充满了动力。😄
演示
首先设备上电,上电后在电脑或手机上可以看到如下热点
点击连接
然后会让你输入密码
这里密码是:1234567879
然后通过浏览器输入:
192.168.169.1
可以看到如下界面,简单写了个配置页面效果:
输入要连接的WIFI名称与密码,点击提交。
成功后返回提交的WIFI名和密码;
介绍一下实现方式和思路
打开AP模式
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
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);
}
}
等待客户端连接即可。为了使设备启动状态更直观,增加了开机亮灯操作。启动后自动打开绿灯。
代码如下:
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
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服务
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
void http_server_task(void* param)
{
// 打开AP
start_ap();
// 启动http服务,也就是响应网页的服务
mhttp_server_init();
}
这里比较简单。
打开ap代码
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信息设置
#define USER_AP_NAME "Ai-M61-32s"
#define USER_AP_PASSWORD "123456789"
然后是http服务的启动
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);
}
}
接下来就是响应请求
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 很简单的界面,可以做很多优化。
static const unsigned char html_page[] = R"(
<html>
<body>
Hello Word!
<form method="post" action="/set">
<input name="ssid" type="text" />
<br/>
<input name="pwd" type="text" />
<br/>
<input type="submit" value="提交"/>
</form>
</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)