[参考] 基于 Ai-M61-32S 的AP网页配网实现 Step 1

[复制链接]
查看2047 | 回复13 | 2023-12-5 20:25:23 | 显示全部楼层 |阅读模式

本帖最后由 WT_0213 于 2023-12-6 10:09 编辑

目前实现上电后自动打开AP模式,连接无线AP后。打开网页提交要连接的WIFI名称 和 WIFI密码 点击提交。相应POST提交的WIFI名称和密码。

以下代码添加了关键点注释,方便大家理解。

发帖积分恢复了,又充满了动力。😄

演示

首先设备上电,上电后在电脑或手机上可以看到如下热点

屏幕截图2023-12-06092220.png

点击连接

屏幕截图2023-12-06092257.png

然后会让你输入密码

屏幕截图2023-12-06092322.png

这里密码是:1234567879

然后通过浏览器输入:

192.168.169.1

可以看到如下界面,简单写了个配置页面效果:

捕获.PNG

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

捕获1.PNG

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

捕获2.PNG

介绍一下实现方式和思路

打开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)

本帖被以下淘专辑推荐:

回复

使用道具 举报

WT_0213 | 2023-12-6 09:33:41 | 显示全部楼层
本帖最后由 WT_0213 于 2023-12-6 10:05 编辑




                               
登录/注册后可看大图


介绍已经更新欢迎食用


                               
登录/注册后可看大图



回复 支持 反对

使用道具 举报

干簧管 | 2023-12-5 21:58:10 | 显示全部楼层
回复

使用道具 举报

san | 2023-12-5 22:49:29 | 显示全部楼层
回复

使用道具 举报

爱笑 | 2023-12-6 08:51:33 | 显示全部楼层
用心做好保姆工作
回复

使用道具 举报

lazy | 2023-12-6 09:57:11 | 显示全部楼层
写的好详细,大赞
回复 支持 反对

使用道具 举报

lazy | 2023-12-6 10:01:28 | 显示全部楼层
希望多一些这样的帖子,现在论坛好多和板子关联性不大。
回复 支持 反对

使用道具 举报

WT_0213 | 2023-12-6 10:11:20 | 显示全部楼层
本帖最后由 WT_0213 于 2023-12-6 10:13 编辑

已更新,还能入园长的法眼吧
回复 支持 反对

使用道具 举报

干簧管 | 2023-12-6 10:19:04 | 显示全部楼层
WT_0213 发表于 2023-12-6 10:11
已更新,还能入园长的法眼吧

回复 支持 反对

使用道具 举报

爱笑 | 2023-12-6 10:24:50 | 显示全部楼层
WT_0213 发表于 2023-12-6 10:11
已更新,还能入园长的法眼吧

优秀~
用心做好保姆工作
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则