这有什么用?

启动 wifi_ap_task 任务将会创建一个软 AP 并启动一个 HTTP 服务器,可使用手机等设备连接到这个软 AP,进入配网网页设置网络的 SSID 和密码,提交后将把数据写入 Flash

sys_task_create_dynamic((const uint8_t *)"WiFi AP", 4096, OS_TASK_PRIORITY(1), wifi_ap_task, NULL);

应用实例

这是一个可联网上报数据的盖革计数器 —— 连接 Wi-Fi 失败进入配网模式,在手机设定网络后重启即可连接 Wi-Fi

示例代码

源代码使用 GNU GPLv3 开源许可协议发布,你可以自由使用、修改和分发源代码,仅需保留原始版权声明和许可证信息,并使用相同的许可证

储存数据

#define USER_DATA_ADDRESS 0x08300000  // 用户数据起始地址
#define PAGE_SIZE         0x400       // 每页大小

/*!
    \brief      向 Flash 写入配网数据
    \param[in]  ssid_data: SSID 数据
    \param[in]  ssid_length: SSID 32 位字长度
    \param[in]  pass_data: 密码数据
    \param[in]  pass_length: 密码 32 位字长度
    \retval     none
*/
void store_user_data(uint32_t *ssid_data, uint32_t ssid_length, uint32_t *pass_data, uint32_t pass_length) {
    // 解锁 Flash 操作
    fmc_unlock();

    // 擦除目标页
    uint32_t page_address = USER_DATA_ADDRESS;
    fmc_page_erase(page_address);

    // 等待擦除完成
    while (fmc_flag_get(FMC_FLAG_BUSY));

    // 写入 SSID 数据
    for (uint32_t i = 0; i < ssid_length; i++) {
        fmc_word_program(page_address + (i * 4), ssid_data[i]);
        while (fmc_flag_get(FMC_FLAG_BUSY));
    }

    // 写入 PASSWORD 数据
    uint32_t pass_start_address = USER_DATA_ADDRESS + (ssid_length * 4);
    for (uint32_t i = 0; i < pass_length; i++) {
        fmc_word_program(pass_start_address + (i * 4), pass_data[i]);
        while (fmc_flag_get(FMC_FLAG_BUSY));
    }

    // 锁定 Flash 操作
    fmc_lock();
}

/*!
    \brief      读取 Flash 中的 SSID
    \param[out] buffer: 欲保存到的字符串
    \param[in]  length: 32 位字长度
    \retval     none
*/
void get_user_ssid(uint32_t *buffer, uint32_t length) {
    // 读取 SSID 数据
    for (uint32_t i = 0; i < length; i++) {
        buffer[i] = *(uint32_t *)(USER_DATA_ADDRESS + (i * 4));
    }
}

/*!
    \brief      读取 Flash 中的密码
    \param[out] buffer: 欲保存到的字符串
    \param[in]  length: 32 位字长度
    \retval     none
*/
void get_user_pass(uint32_t *buffer, uint32_t length) {
    // 计算 PASSWORD 的起始地址
    uint32_t ssid_max_length = 4;  // SSID 最大长度为 16 字节,即 4 个 32 位字
    uint32_t pass_start_address = USER_DATA_ADDRESS + (ssid_max_length * 4);

    // 读取 PASSWORD 数据
    for (uint32_t i = 0; i < length; i++) {
        buffer[i] = *(uint32_t *)(pass_start_address + (i * 4));
    }
}

/*!
    \brief      向 Flash 写入 SSID
    \param[in]  ssid: SSID
    \retval     none
*/
void set_user_ssid(const char *ssid) {
    uint32_t ssid_data[4] = {0};  // 16 字节数据,存储为 4 个 32 位字

    // 将字符串转换为字节数组
    for (uint32_t i = 0; i < 4; i++) {
        if (i * 4 < strlen(ssid)) {
            ssid_data[i] = *(uint32_t *)(ssid + (i * 4));
        }
    }

    // 获取当前存储的 PASSWORD 数据
    uint32_t pass_data[16];
    get_user_pass(pass_data, 16);

    // 存储 SSID 和 PASSWORD 数据
    store_user_data(ssid_data, 4, pass_data, 16);
}

/*!
    \brief      向 Flash 写入密码
    \param[in]  pass: 密码
    \retval     none
*/
void set_user_pass(const char *pass) {
    uint32_t pass_data[16] = {0};  // 63 字节数据,存储为 16 个 32 位字

    // 将字符串转换为字节数组
    for (uint32_t i = 0; i < 16; i++) {
        if (i * 4 < strlen(pass)) {
            pass_data[i] = *(uint32_t *)(pass + (i * 4));
        }
    }

    // 获取当前存储的 SSID 数据
    uint32_t ssid_data[4];
    get_user_ssid(ssid_data, 4);

    // 存储 SSID 和 PASSWORD 数据
    store_user_data(ssid_data, 4, pass_data, 16);
}

软 AP 和 HTTP Server

// 软 AP 信息
#define AP_SSID     "GD32_AP"
#define AP_PASSWORD "52GD32IoT"
#define HTTP_PORT   80

// 默认 Wi-Fi
char wifiSSID[16] = "SSID";
char wifiPASS[64] = "PASS";

/*!
    \brief      解析 POST 参数
    \param[in]  input: POST 数据
    \param[in]  key: 欲提取的键
    \param[out] value: 欲保存到的变量
    \retval     true 表示解析成功,false 表示解析失败
*/
int get_param(const char* input, const char* key, char* value) {
    char search_key[64] = {0};
    snprintf(search_key, sizeof(search_key), "%s=", key);

    const char* start = strstr(input, search_key);
    if (start != NULL) {
        start += strlen(search_key);  // 跳过 "key="

        const char* end = strchr(start, '&');
        if (end != NULL) {
            // 如果找到 & 分隔符,复制到分隔符位置
            size_t len = end - start;
            memcpy(value, start, len);
            value[len] = '\0';
        } else {
            // 如果没找到 &,说明是最后一个参数,直接复制到末尾
            strcpy(value, start);
        }
        return true;
    }
    return false;
}

/*!
    \brief      处理 HTTP 请求
    \param[in]  conn_fd: 套接字描述符
    \retval     none
*/
static void http_server_netconn_serve(int conn_fd) {
    char buffer[1024];
    int len = read(conn_fd, buffer, sizeof(buffer) - 1);

    if (len > 0) {
        buffer[len] = '\0';
        printf("Received request:\n%s\n", buffer);

        // 判断请求方法
        if (strncmp(buffer, "GET", 3) == 0) {
            // GET 请求,返回包含表单的 HTML 页面
            const char *response =
                "HTTP/1.1 200 OK\r\n"
                "Content-Type: text/html\r\n"
                "Connection: close\r\n"
                "\r\n"
                "<html><head>"
                "<title>Wi-Fi Config</title><meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />"
                "<style>body{margin:0;padding:20px;background-color:#f0f0f0;}h1{text-align:center;}"
                "form{margin:0 auto;background-color:#fff;padding: 20px;border-radius:5px;}"
                "div{margin-bottom:15px;}p{color:gray;font-size:10px;text-align:center;}"
                "input[type='text']{width:100%;padding:8px;border:1px solid #ddd;}"
                "input[type='submit']{width:100%;padding:10px;background-color:#d3d3d3;color:#fff;border:none;}"
                "input[type='submit']:hover{background-color: #808080;}</style></head>"
                "<body><h1>Wi-Fi Config</h1>"
                "<form method='POST' action='/'>"
                "<div><label for='ssid'>SSID:</label>"
                "<input type='text' name='ssid' placeholder='Enter SSID'></div>"
                "<div><label for='pass'>Password:</label>"
                "<input type='text' name='pass' placeholder='Enter PASSWORD'></div>"
                "<input type='submit' value='Save'>"
                "</form></body></html>";

            write(conn_fd, response, strlen(response));
            printf("HTTP response sent.\n");
        } else if (strncmp(buffer, "POST", 4) == 0) {
            // POST 请求,处理表单数据
            char *content_type = strstr(buffer, "Content-Type: ");
            if (content_type != NULL) {
                content_type += 13; // 跳过 "Content-Type: "
                if (strstr(content_type, "application/x-www-form-urlencoded") != NULL) {
                    // 找到请求体的起始位置
                    char *body = strstr(buffer, "\r\n\r\n");
                    if (body != NULL) {
                        body += 4; // 跳过 "\r\n\r\n"
                        printf("Received POST data: %s\n", body);

                        // 解析 POST 数据
                        char ssid[16] = {0};
                        char pass[64] = {0};

                        get_param(body, "ssid", ssid);
                        get_param(body, "pass", pass);
                        printf("SSID: %s\n", ssid);
                        printf("Password: %s\n", pass);
                        set_user_ssid(ssid);
                        set_user_pass(pass);

                        // 返回响应
                        const char *response =
                            "HTTP/1.1 200 OK\r\n"
                            "Content-Type: text/html\r\n"
                            "Connection: close\r\n"
                            "\r\n"
                            "<html><head><title>FGC Wi-Fi Config</title><meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />"
                            "<style>body{margin:0;padding:20px;background-color:#f0f0f0;}h1{text-align:center;}"
                            "p{color:gray;font-size:10px;text-align:center;}"
                            "input[type='submit']{width:100%;padding:10px;background-color:#d3d3d3;color:#fff;border:none;}"
                            "input[type='submit']:hover{background-color: #808080;}</style></head>"
                            "<body><h1>Wi-Fi Configured</h1>"
                            "<p>Please reboot your device.</p>"
                            "<input type='submit' value='< BACK' onclick='history.back()'>";

                        write(conn_fd, response, strlen(response));
                        printf("HTTP response sent.\n");
                    }
                }
            }
        } else {
            // 其他请求方法,返回 405 Method Not Allowed
            const char *response =
                "HTTP/1.1 405 Method Not Allowed\r\n"
                "Content-Type: text/html\r\n"
                "Connection: close\r\n"
                "\r\n"
                "<html><head><title>405 Method Not Allowed</title></head>"
                "<body><h1>405 Method Not Allowed</h1></body></html>";

            write(conn_fd, response, strlen(response));
            printf("HTTP response sent.\n");
        }
    } else {
        printf("No data received.\n");
    }

    close(conn_fd);
    printf("Client connection closed.\n");
}

/*!
    \brief      HTTP Server 任务
    \param[in]  arg: arg
    \retval     none
*/
static void http_server_task(void *arg) {
    int listen_fd, conn_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    // 创建 socket
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) {
        printf("Failed to create socket.\n");
        return;
    }

    printf("Socket created successfully.\n");

    // 配置 socket 地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(HTTP_PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 绑定 socket
    if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        printf("Failed to bind socket.\n");
        close(listen_fd);
        return;
    }

    printf("Socket bound to port %d.\n", HTTP_PORT);

    // 监听连接
    if (listen(listen_fd, 5) < 0) {
        printf("Failed to listen on socket.\n");
        close(listen_fd);
        return;
    }

    printf("HTTP server is listening on port %d\n", HTTP_PORT);

    while (1) {
        // 接受连接
        conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_addr_len);
        if (conn_fd < 0) {
            printf("Failed to accept connection.\n");
            continue;
        }

        printf("New client connected. IP: %s\n", inet_ntoa(client_addr.sin_addr));

        // 处理 HTTP 请求
        http_server_netconn_serve(conn_fd);
    }
}


/*!
    \brief      Wi-Fi SoftAP 任务
    \param[in]  param: param
    \retval     none
*/
static void wifi_ap_task(void *param) {
    // 启动软 AP
    printf("Starting soft AP...\n");
    if (wifi_management_ap_start((char *)AP_SSID, (char *)AP_PASSWORD, 1, AUTH_MODE_WPA, 0) != 0) {
        printf("Failed to start soft AP.\n");
        goto exit;
    }

    printf("Soft AP started. SSID: %s, Password: %s\n", AP_SSID, AP_PASSWORD);

    // 启动 HTTP 服务器
    printf("Starting HTTP server...\n");
    http_server_task(NULL);

exit:
    printf("The ap task has ended.\n");
    sys_task_delete(NULL);
}