ESP8266 NonOS-SDK Web配网

参考博客:[网络篇]ESP8266-SDK教程(六)之网页配置Wi-Fi名称和密码

一、原理

        ESP8266 设置为 STATION + AP 模式。
        STATION 模式又叫做站点工作模式,类似于无线终端,处于 STATION 模式下的 ESP8266,可以连接到 AP。通过 STATION (简称为“STA”)模式,ESP8266 作为客户端连接到路由的 Wi-Fi 信号。
        AP 就是 Access Point 接入点。由 ESP8266 自己开启热点,供别的设备接入,组成一个局域网。

        流程就是这个样子,手机 / 电脑连接 ESP8266 开启的热点,在浏览器打开配网网页,通过网页发送 SSID 和 PWD 给 ESP8266,ESP8266 解析以后得到 SSID 和 PWD 去连接 Wi-Fi。

        我们需要将这个写好的网页,保存到 ESP8266 当中,当 ESP8266 收到 http 请求时,我们再将这个网页发送给浏览器,此时 ESP8266 就是一个很小的 Web Server,处理来自浏览器的 http 请求,然后按照一定格式返回具体的网页,或者其他数据,这些都是通过 TCP 传输的。

二、程序解析

程序我们可以通过 NonOS-SDK 自带的 WebServer 进行改动。

1、初始化 WebServer

//初始化webserver,webserver端口为80,可以通过初始化时传入
void ICACHE_FLASH_ATTR
www_webserver_init(uint32 port) {
	LOCAL struct espconn esp_conn;
	LOCAL esp_tcp esptcp;

	esp_conn.type = ESPCONN_TCP;
	esp_conn.state = ESPCONN_NONE;
	esp_conn.proto.tcp = &esptcp;
	esp_conn.proto.tcp->local_port = port;
	espconn_regist_connectcb(&esp_conn, www_webserver_listen);
	espconn_accept(&esp_conn);
}

2、初始化 SoftAP

/*
 * softAP模式初始化代码
 */
void ICACHE_FLASH_ATTR
www_softAP_init(void){
	struct softap_config soft_ap_Config;

	//wifi_set_opmode_current(SOFTAP_MODE);//设置为AP模式,不保存到flash
	wifi_set_opmode(STATIONAP_MODE);//设置为STATION+AP模式,并保存到flash

	os_memset(&soft_ap_Config, 0, sizeof(struct softap_config)); // AP参数结构体 = 0

	soft_ap_Config.ssid_len = os_strlen(AP_NAME);//热点名称长度,与你实际的名称长度一致就好
	os_strcpy(soft_ap_Config.ssid, AP_NAME);//实际热点名称设置,可以根据你的需要来
	os_strcpy(soft_ap_Config.password, AP_PWD);//热点密码设置
	soft_ap_Config.authmode = AUTH_OPEN;//若是你的密码为空,加密模式为AUTH_WPA2_PSK,那么ESP8266不会使用你设置的WIFI名称,而是系统生成,因此没有密码就把加密模式改成AUTH_OPEN
	soft_ap_Config.beacon_interval = 100;//信标间隔,默认为100
	soft_ap_Config.channel = 1;//信道,共支持1~13个信道
	soft_ap_Config.max_connection = 4;//最大连接数量,最大支持四个,默认四个
	soft_ap_Config.ssid_hidden = 0;//隐藏SSID,0:不隐藏  1:隐藏
	//wifi_softap_set_config_current(&soft_ap_Config);//设置 Wi-Fi SoftAP 接口配置,不保存到 Flash
	wifi_softap_set_config(&soft_ap_Config);//设置 Wi-Fi SoftAP 接口配置,保存到 Flash
}

3、绑定回调函数

//断开重连回调函数
LOCAL ICACHE_FLASH_ATTR
void www_webserver_recon(void *arg, sint8 err)
{
    struct espconn *pesp_conn = arg;

    os_printf("webserver's %d.%d.%d.%d:%d err %d reconnect\n", pesp_conn->proto.tcp->remote_ip[0],
    		pesp_conn->proto.tcp->remote_ip[1],pesp_conn->proto.tcp->remote_ip[2],
    		pesp_conn->proto.tcp->remote_ip[3],pesp_conn->proto.tcp->remote_port, err);
}

//断开连接回调函数
LOCAL ICACHE_FLASH_ATTR
void www_webserver_discon(void *arg)
{
    struct espconn *pesp_conn = arg;

    os_printf("webserver's %d.%d.%d.%d:%d disconnect\n", pesp_conn->proto.tcp->remote_ip[0],
        		pesp_conn->proto.tcp->remote_ip[1],pesp_conn->proto.tcp->remote_ip[2],
        		pesp_conn->proto.tcp->remote_ip[3],pesp_conn->proto.tcp->remote_port);
}


//绑定回调函数
LOCAL void ICACHE_FLASH_ATTR
www_webserver_listen(void *arg) {
	struct espconn *pesp_conn = arg;

	espconn_regist_recvcb(pesp_conn, www_webserver_recv);//数据接收回调函数
	espconn_regist_reconcb(pesp_conn, www_webserver_recon);//断开重连回调函数
	espconn_regist_disconcb(pesp_conn, www_webserver_discon);//断开连接回调函数
}

4、数据接收回调函数

//数据接收回调函数
LOCAL void ICACHE_FLASH_ATTR
www_webserver_recv(void *arg, char *pusrdata, unsigned short length) {
	URL_Frame *pURL_Frame = NULL;
	char *pParseBuffer = NULL;
	bool parse_flag = false;
	struct espconn *ptrespconn = arg;
	uint8 sta;

	os_printf("len:%u\n", length);
	if (www_check_data(pusrdata, length) == false) {
		os_printf("goto\n");
		goto _temp_exit;
	}

	parse_flag = www_save_data(pusrdata, length);
	if (parse_flag == false) {
		www_response_send(ptrespconn, false);
	}


	//os_printf(buffer);
	pURL_Frame = (URL_Frame *) os_zalloc(sizeof(URL_Frame));
	www_parse_url(buffer, pURL_Frame);

	//os_printf("recv:%s\n", pusrdata);
	os_printf("\r\nType[%d]\r\n", pURL_Frame->Type);
	os_printf("pSelect[%s]\r\n", pURL_Frame->pSelect);
	os_printf("pCommand[%s]\r\n", pURL_Frame->pCommand);
	os_printf("pFilename[%s]\r\n", pURL_Frame->pFilename);

	switch (pURL_Frame->Type) {
		case GET:
			os_printf("We have a GET request.\n");
			os_printf("pURL_Frame->pFilename: %s\n",pURL_Frame->pFilename);

			sta = wifi_station_get_connect_status();

			os_printf("wifi_station_get_connect_status: %d\n", sta);

			if(pURL_Frame->pFilename[0] == 0){
				www_data_send(ptrespconn, true, page_html);
			}
			if(strncmp(pURL_Frame->pFilename, "WebConfig.html", strlen("WebConfig.html")) == 0){
				www_data_send(ptrespconn, true, config_html);
			}

			break;

		case POST:
			os_printf("We have a POST request.\n");
			pParseBuffer = (char *)os_strstr(buffer, "\r\n\r\n");

			sta = wifi_station_get_connect_status();
			os_printf("wifi_station_get_connect_status: %d\n", sta);

			if(sta == STATION_GOT_IP){
				//www_data_send(ptrespconn, true, "<meta charset='UTF-8'>Connect Success!");
				www_data_send(ptrespconn, true, finish_html);
			}
			else if(sta == STATION_WRONG_PASSWORD){
				//www_data_send(ptrespconn, true, "<meta charset='UTF-8'>Wrong Password!");
				www_data_send(ptrespconn, true, finish_html);
			}
			else{
				www_data_send(ptrespconn, true, config_html);
			}

			if(strncmp(pURL_Frame->pCommand, "connect-wifi", strlen("connect-wifi")) == 0){
				webconfig_get_wifi_ssid_pwd(pusrdata);
			}


			if (pParseBuffer == NULL) {
			     break;

			}

			os_printf("%s\r\n", pParseBuffer);

			break;
	}

	if (buffer != NULL) {
		os_free(buffer);
		buffer = NULL;
	}
	os_free(pURL_Frame);
	pURL_Frame = NULL;
	_temp_exit: ;
}

5、获取网页传过来的 WiFi 账号和密码

/* 功  能:将str字符串中的oldstr字符串替换为newstr字符串
 * 参  数:str:操作目标 oldstr:被替换者 newstr:替换者
 * 返回值:返回替换之后的字符串
 * 版  本: V0.2
 */
static char * ICACHE_FLASH_ATTR
strrpc(char *str, char *oldstr, char *newstr){
    char bstr[os_strlen(str)];//转换缓冲区
    int len = os_strlen(str);
    int i, len2 = 0;
    
    os_memset(bstr, 0, sizeof(bstr));

    for(i = 0; i < len; i++){
        if(!os_strncmp(str + i, oldstr, os_strlen(oldstr))){//查找目标字符串
        	strcat(bstr, newstr);
        	len2 = os_strlen(oldstr);
            i += len2 - 1;
        }else{
        	strncat(bstr, str + i, 1);//保存一字节进缓冲区
	    }
    }

    os_strcpy(str,bstr);
    return str;
}

//获取网页传过来的 WiFi 账号和密码
static void ICACHE_FLASH_ATTR
webconfig_get_wifi_ssid_pwd(char* urlparam)
{
    char *p = NULL, *q = NULL;
    char ssid[32], pass[64];

    os_memset(ssid, 0, sizeof(ssid));
    os_memset(pass, 0, sizeof(pass));

    p = (char *)os_strstr(urlparam, "SSID=");
    q = (char *)os_strstr(urlparam, "PASSWORD=");
    if ( p == NULL || q == NULL ){
        return;
    }
    os_memcpy(ssid, p + 5, q - p - 6);
    os_memcpy(pass, q + 9, os_strlen(urlparam) - (q - urlparam) - 9);

    //空格在url参数会转码成+号,+号转码成%2B
    strrpc(ssid, "+", " ");

    os_printf("ssid[%s], pass[%s]\r\n", ssid, pass);

    wifi_set_opmode(STATIONAP_MODE);
    struct station_config stConf;
    stConf.bssid_set = 0;
    os_memset(&stConf.ssid, 0, sizeof(stConf.ssid));
    os_memset(&stConf.password, 0, sizeof(stConf.password));

    os_memcpy(&stConf.ssid, ssid, os_strlen(ssid));
    os_memcpy(&stConf.password, pass, os_strlen(pass));

    wifi_station_set_config(&stConf);

    //重启
    system_restart();
}

6、数据保存和检查函数

LOCAL char *buffer;
static uint32 sumlength = 0;

LOCAL bool ICACHE_FLASH_ATTR
www_save_data(char *precv, uint16 length)
{
    bool flag = false;
    char length_buf[10] = {0};
    char *ptemp = NULL;
    char *pdata = NULL;
    uint16 headlength = 0;
    static uint32 totallength = 0;

    ptemp = (char *)os_strstr(precv, "\r\n\r\n");

    if (ptemp != NULL) {
        length -= ptemp - precv;
        length -= 4;
        totallength += length;
        headlength = ptemp - precv + 4;
        pdata = (char *)os_strstr(precv, "Content-Length: ");

        if (pdata != NULL) {
            pdata += 16;
            buffer = (char *)os_strstr(pdata, "\r\n");

            if (buffer != NULL) {
                os_memcpy(length_buf, pdata, buffer - pdata);
                sumlength = atoi(length_buf);
            }
        } else {
        	if (totallength != 0x00){
        		totallength = 0;
        		sumlength = 0;
        		return false;
        	}
        }
        if ((sumlength + headlength) >= 1024) {
        	buffer = (char *)os_zalloc(headlength + 1);
            os_memcpy(buffer, precv, headlength + 1);
        } else {
        	buffer = (char *)os_zalloc(sumlength + headlength + 1);
        	os_memcpy(buffer, precv, os_strlen(precv));
        }
    } else {
        if (buffer != NULL) {
            totallength += length;
            os_memcpy(buffer + os_strlen(buffer), precv, length);
        } else {
            totallength = 0;
            sumlength = 0;
            return false;
        }
    }

    if (totallength == sumlength) {
        totallength = 0;
        sumlength = 0;
        return true;
    } else {
        return false;
    }
}

LOCAL bool ICACHE_FLASH_ATTR
www_check_data(char *precv, uint16 length)
{
        //bool flag = true;
    char length_buf[10] = {0};
    char *ptemp = NULL;
    char *pdata = NULL;
    char *tmp_precvbuffer;
    uint16 tmp_length = length;
    uint32 tmp_totallength = 0;

    ptemp = (char *)os_strstr(precv, "\r\n\r\n");

    if (ptemp != NULL) {
        tmp_length -= ptemp - precv;
        tmp_length -= 4;
        tmp_totallength += tmp_length;

        pdata = (char *)os_strstr(precv, "Content-Length: ");

        if (pdata != NULL){
            pdata += 16;
            tmp_precvbuffer = (char *)os_strstr(pdata, "\r\n");

            if (tmp_precvbuffer != NULL){
                os_memcpy(length_buf, pdata, tmp_precvbuffer - pdata);
                sumlength = atoi(length_buf);
                os_printf("A_dat:%u,tot:%u,lenght:%u\n",sumlength,tmp_totallength,tmp_length);
                if(sumlength != tmp_totallength){
                    return false;
                }
            }
        }
    }
    return true;
}

7、解析网页函数

LOCAL void ICACHE_FLASH_ATTR
www_parse_url(char *precv, URL_Frame *purl_frame)
{
    char *str = NULL;
    uint8 length = 0;
    char *pbuffer = NULL;
    char *pbufer = NULL;

    if (purl_frame == NULL || precv == NULL) {
        return;
    }

    pbuffer = (char *)os_strstr(precv, "Host:");

    if (pbuffer != NULL) {
        length = pbuffer - precv;
        pbufer = (char *)os_zalloc(length + 1);
        pbuffer = pbufer;
        os_memcpy(pbuffer, precv, length);
        os_memset(purl_frame->pSelect, 0, URLSize);
        os_memset(purl_frame->pCommand, 0, URLSize);
        os_memset(purl_frame->pFilename, 0, URLSize);

        if (os_strncmp(pbuffer, "GET ", 4) == 0) {
            purl_frame->Type = GET;
            pbuffer += 4;
        } else if (os_strncmp(pbuffer, "POST ", 5) == 0) {
            purl_frame->Type = POST;
            pbuffer += 5;
        }

        pbuffer ++;
        str = (char *)os_strstr(pbuffer, "HTTP");

        if (str != NULL) {
            length = str - pbuffer - 1;
            os_memcpy(purl_frame->pFilename, pbuffer, length);
        }

        os_free(pbufer);
    } else {
        return;
    }

    pbuffer = (char *)os_strstr(precv, "SSID");
    if (pbuffer != NULL) {
        purl_frame->Type = POST;
        os_memcpy(purl_frame->pCommand, "connect-wifi", strlen("connect-wifi"));
        os_free(pbufer);
    }
}

8、服务端写数据函数

//发送html网页数据
LOCAL void ICACHE_FLASH_ATTR
www_data_send(void *arg, bool responseOK, char *psend)
{
    uint16 length = 0;
    char *pbuf = NULL;
    char httphead[256];
    struct espconn *ptrespconn = arg;
    os_memset(httphead, 0, 256);

    if (responseOK) {
        os_sprintf(httphead,
                   "HTTP/1.0 200 OK\r\nContent-Length: %d\r\nServer: lwIP/1.4.0\r\n",
                   psend ? os_strlen(psend) : 0);

        if (psend) {
            os_sprintf(httphead + os_strlen(httphead),
                       "Content-type: text/html\r\nExpires: Fri, 10 Apr 2008 14:00:00 GMT\r\nPragma: no-cache\r\n\r\n");
            length = os_strlen(httphead) + os_strlen(psend);
            pbuf = (char *)os_zalloc(length + 1);
            os_memcpy(pbuf, httphead, os_strlen(httphead));
            os_memcpy(pbuf + os_strlen(httphead), psend, os_strlen(psend));
        } else {
            os_sprintf(httphead + os_strlen(httphead), "\n");
            length = os_strlen(httphead);
        }
    } else {
        os_sprintf(httphead, "HTTP/1.0 400 BadRequest\r\n\
Content-Length: 0\r\nServer: lwIP/1.4.0\r\n\n");
        length = os_strlen(httphead);
    }

    if (psend) {
#ifdef SERVER_SSL_ENABLE
        espconn_secure_sent(ptrespconn, pbuf, length);
#else
        espconn_sent(ptrespconn, pbuf, length);
#endif
    } else {
#ifdef SERVER_SSL_ENABLE
        espconn_secure_sent(ptrespconn, httphead, length);
#else
        espconn_sent(ptrespconn, httphead, length);
#endif
    }

    if (pbuf) {
        os_free(pbuf);
        pbuf = NULL;
    }
}

//发送json数据
LOCAL void ICACHE_FLASH_ATTR
www_data_send_json(void *arg, bool responseOK, char *psend)
{
    uint16 length = 0;
    char *pbuf = NULL;
    char httphead[256];
    struct espconn *ptrespconn = arg;
    os_memset(httphead, 0, 256);

    if (responseOK) {
        os_sprintf(httphead,
                   "HTTP/1.0 200 OK\r\nContent-Length: %d\r\nServer: lwIP/1.4.0\r\n",
                   psend ? os_strlen(psend) : 0);

        if (psend) {
            os_sprintf(httphead + os_strlen(httphead),
                       "Content-type: application/json\r\nExpires: Fri, 10 Apr 2008 14:00:00 GMT\r\nPragma: no-cache\r\n\r\n");
            length = os_strlen(httphead) + os_strlen(psend);
            pbuf = (char *)os_zalloc(length + 1);
            os_memcpy(pbuf, httphead, os_strlen(httphead));
            os_memcpy(pbuf + os_strlen(httphead), psend, os_strlen(psend));
        } else {
            os_sprintf(httphead + os_strlen(httphead), "\n");
            length = os_strlen(httphead);
        }
    } else {
        os_sprintf(httphead, "HTTP/1.0 400 BadRequest\r\n\
Content-Length: 0\r\nServer: lwIP/1.4.0\r\n\n");
        length = os_strlen(httphead);
    }

    if (psend) {
#ifdef SERVER_SSL_ENABLE
        espconn_secure_sent(ptrespconn, pbuf, length);
#else
        espconn_sent(ptrespconn, pbuf, length);
#endif
    } else {
#ifdef SERVER_SSL_ENABLE
        espconn_secure_sent(ptrespconn, httphead, length);
#else
        espconn_sent(ptrespconn, httphead, length);
#endif
    }

    if (pbuf) {
        os_free(pbuf);
        pbuf = NULL;
    }
}

//发送响应成功数据
LOCAL void ICACHE_FLASH_ATTR
www_response_send(void *arg, bool responseOK)
{
    struct espconn *ptrespconn = arg;

    www_data_send(ptrespconn, responseOK, NULL);
}

9、网页数据

不知道为啥,meta 标签里我都设置为 utf-8 了,中文输出还是乱码,所以网页里用的全部都是英文。

//欢迎页面代码
char* page_html = "<!DOCTYPE html>\r\n"
		"<html>\r\n"
		"    <head>\r\n"
		"        <meta charset=\"utf-8\" content=\"width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\" name=\"viewport\"/>\r\n"
		"        <title>\r\n"
		"            ZCM IOT WiFi Config\r\n"
		"        </title>\r\n"
		"    </head>\r\n"
		"    <body style=\"background: #00F948\">\r\n"
		"        <div align=\"center\"><br/><br/>\r\n"
		"            <font>\r\n"
		"                ZCM IOT WiFi Config\r\n"
		"            </font>\r\n"
		"            <br/>\r\n"
		"            <font>\r\n"
		"                E-mail: wxhntmy@163.com\r\n"
		"            </font>\r\n"
		"            <br/>\r\n"
		"            <br/>\r\n"
		"            <a href=\"WebConfig.html\" text-decoration=\"none\">\r\n"
		"                <button formtarget=\"_self\" style=\"display:block;margin:0 auto\">\r\n"
		"                    Start Config\r\n"
		"                </button>\r\n"
		"            </a>\r\n"
		"			<br/>\r\n"
		"        </div>\r\n"
		"    </body>\r\n"
		"</html>";
//配网页面
char* config_html = "<!DOCTYPE html>\r\n"
		"<html>\r\n"
		"    <head>\r\n"
		"        <meta charset=\"utf-8\" content=\"width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\" name=\"viewport\"/>\r\n"
		"        <title>\r\n"
		"            ZCM IOT WiFi Config\r\n"
		"        </title>\r\n"
		"    </head>\r\n"
		"    <body style=\"background: #00F948\">\r\n"
		"        <div align=\"center\">\r\n"
		"			<br><br>\r\n"
		"            <font>\r\n"
		"                ZCM IOT WiFi Config\r\n"
		"            </font>\r\n"
		"            <br/>\r\n"
		"            <font>\r\n"
		"                E-mail: wxhntmy@163.com\r\n"
		"            </font>\r\n"
		"            <br/>\r\n"
		"            <br/>\r\n"
		"        </div>\r\n"
		"        <form action=\"WiFiConfig.html\" enctype=\"application/x-www-form-urlencoded\" method=\"post\">\r\n"
		"            <table align=\"center\" border=\"0\" cellspacing=\"10\">\r\n"
		"                <tr>\r\n"
		"                    <td>\r\n"
		"                        Wi-Fi Name:\r\n"
		"                        <input name=\"SSID\" placeholder=\"input Wi-Fi name\" type=\"text\"/>\r\n"
		"                    </td>\r\n"
		"                </tr>\r\n"
		"                <tr>\r\n"
		"                    <td>\r\n"
		"                        Wi-Fi&nbsp;&nbsp;&nbsp;&nbsp;Pwd:\r\n"
		"                        <input name=\"PASSWORD\" placeholder=\"input Wi-Fi Password\" type=\"password\"/>\r\n"
		"                    </td>\r\n"
		"                </tr>\r\n"
		"            </table>\r\n"
		"            <button style=\"display:block;margin:0 auto\" type=\"submit\" value=\"Submit\">\r\n"
		"                Confirm\r\n"
		"            </button>\r\n"
		"        </form>\r\n"
		"    </body>\r\n"
		"</html>";


//配网完成
char* finish_html = "<!DOCTYPE html>\r\n"
		"<html>\r\n"
		"    <head>\r\n"
		"        <meta charset=\"utf-8\" content=\"width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\" name=\"viewport\"/>\r\n"
		"        <title>\r\n"
		"            ZCM IOT WiFi Config\r\n"
		"        </title>\r\n"
		"    </head>\r\n"
		"    <body style=\"background: #00F948\">\r\n"
		"        <div align=\"center\">\r\n"
		"            <br><br>\r\n"
		"            <font>\r\n"
		"                ZCM IOT WiFi Config\r\n"
		"            </font>\r\n"
		"            <br/>\r\n"
		"            <font>\r\n"
		"                E-mail: wxhntmy@163.com\r\n"
		"            </font>\r\n"
		"            <br/>\r\n"
		"            <br/>\r\n"
		"            <font>\r\n"
		"                Connecting to Wi-Fi, the connection is complete after the LED flashes three times!\r\n"
		"                <br/>\r\n"
		"                Steady on means the connection failed, please re-enter!\r\n"
		"                <br/><br>\r\n"
		"                <a href=\"WebConfig.html\" text-decoration=\"none\">\r\n"
		"                    <button formtarget=\"_self\" style=\"display:block;margin:0 auto\">\r\n"
		"                        Re-distribution Network\r\n"
		"                    </button>\r\n"
		"                </a>\r\n"
		"                <br/>\r\n"
		"                <a href=\"/\">\r\n"
		"                    Back Home\r\n"
		"                </a>\r\n"
		"            </font>\r\n"
		"        </div>\r\n"
		"    </body>\r\n"
		"</html>";

10、完整代码

/*
 * webserver.c
 *
 *  Created on: 2021年4月8日
 *      Author: wxhntmy
 */

#include "espconn.h"
#include "mem.h"
#include "ets_sys.h"
#include "osapi.h"

#include "user_interface.h"

#include "user_devicefind.h"
#include "user_webserver.h"
#include "user_esp_platform.h"
#include "user_tcpclient.h"


#include "webserver.h"	//头文件只声明了几个函数

char* AP_NAME = "zcm_iot_wifi"; //wifi名字
char* AP_PWD = "123456"; //wifi密码

//欢迎页面代码
char* page_html = "<!DOCTYPE html>\r\n"
		"<html>\r\n"
		"    <head>\r\n"
		"        <meta charset=\"utf-8\" content=\"width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\" name=\"viewport\"/>\r\n"
		"        <title>\r\n"
		"            ZCM IOT WiFi Config\r\n"
		"        </title>\r\n"
		"    </head>\r\n"
		"    <body style=\"background: #00F948\">\r\n"
		"        <div align=\"center\"><br/><br/>\r\n"
		"            <font>\r\n"
		"                ZCM IOT WiFi Config\r\n"
		"            </font>\r\n"
		"            <br/>\r\n"
		"            <font>\r\n"
		"                E-mail: wxhntmy@163.com\r\n"
		"            </font>\r\n"
		"            <br/>\r\n"
		"            <br/>\r\n"
		"            <a href=\"WebConfig.html\" text-decoration=\"none\">\r\n"
		"                <button formtarget=\"_self\" style=\"display:block;margin:0 auto\">\r\n"
		"                    Start Config\r\n"
		"                </button>\r\n"
		"            </a>\r\n"
		"			<br/>\r\n"
		"        </div>\r\n"
		"    </body>\r\n"
		"</html>";
//配网页面
char* config_html = "<!DOCTYPE html>\r\n"
		"<html>\r\n"
		"    <head>\r\n"
		"        <meta charset=\"utf-8\" content=\"width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\" name=\"viewport\"/>\r\n"
		"        <title>\r\n"
		"            ZCM IOT WiFi Config\r\n"
		"        </title>\r\n"
		"    </head>\r\n"
		"    <body style=\"background: #00F948\">\r\n"
		"        <div align=\"center\">\r\n"
		"			<br><br>\r\n"
		"            <font>\r\n"
		"                ZCM IOT WiFi Config\r\n"
		"            </font>\r\n"
		"            <br/>\r\n"
		"            <font>\r\n"
		"                E-mail: wxhntmy@163.com\r\n"
		"            </font>\r\n"
		"            <br/>\r\n"
		"            <br/>\r\n"
		"        </div>\r\n"
		"        <form action=\"WiFiConfig.html\" enctype=\"application/x-www-form-urlencoded\" method=\"post\">\r\n"
		"            <table align=\"center\" border=\"0\" cellspacing=\"10\">\r\n"
		"                <tr>\r\n"
		"                    <td>\r\n"
		"                        Wi-Fi Name:\r\n"
		"                        <input name=\"SSID\" placeholder=\"input Wi-Fi name\" type=\"text\"/>\r\n"
		"                    </td>\r\n"
		"                </tr>\r\n"
		"                <tr>\r\n"
		"                    <td>\r\n"
		"                        Wi-Fi&nbsp;&nbsp;&nbsp;&nbsp;Pwd:\r\n"
		"                        <input name=\"PASSWORD\" placeholder=\"input Wi-Fi Password\" type=\"password\"/>\r\n"
		"                    </td>\r\n"
		"                </tr>\r\n"
		"            </table>\r\n"
		"            <button style=\"display:block;margin:0 auto\" type=\"submit\" value=\"Submit\">\r\n"
		"                Confirm\r\n"
		"            </button>\r\n"
		"        </form>\r\n"
		"    </body>\r\n"
		"</html>";


//配网完成
char* finish_html = "<!DOCTYPE html>\r\n"
		"<html>\r\n"
		"    <head>\r\n"
		"        <meta charset=\"utf-8\" content=\"width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\" name=\"viewport\"/>\r\n"
		"        <title>\r\n"
		"            ZCM IOT WiFi Config\r\n"
		"        </title>\r\n"
		"    </head>\r\n"
		"    <body style=\"background: #00F948\">\r\n"
		"        <div align=\"center\">\r\n"
		"            <br><br>\r\n"
		"            <font>\r\n"
		"                ZCM IOT WiFi Config\r\n"
		"            </font>\r\n"
		"            <br/>\r\n"
		"            <font>\r\n"
		"                E-mail: wxhntmy@163.com\r\n"
		"            </font>\r\n"
		"            <br/>\r\n"
		"            <br/>\r\n"
		"            <font>\r\n"
		"                Connecting to Wi-Fi, the connection is complete after the LED flashes three times!\r\n"
		"                <br/>\r\n"
		"                Steady on means the connection failed, please re-enter!\r\n"
		"                <br/><br>\r\n"
		"                <a href=\"WebConfig.html\" text-decoration=\"none\">\r\n"
		"                    <button formtarget=\"_self\" style=\"display:block;margin:0 auto\">\r\n"
		"                        Re-distribution Network\r\n"
		"                    </button>\r\n"
		"                </a>\r\n"
		"                <br/>\r\n"
		"                <a href=\"/\">\r\n"
		"                    Back Home\r\n"
		"                </a>\r\n"
		"            </font>\r\n"
		"        </div>\r\n"
		"    </body>\r\n"
		"</html>";

LOCAL void ICACHE_FLASH_ATTR
www_data_send(void *arg, bool responseOK, char *psend)
{
    uint16 length = 0;
    char *pbuf = NULL;
    char httphead[256];
    struct espconn *ptrespconn = arg;
    os_memset(httphead, 0, 256);

    if (responseOK) {
        os_sprintf(httphead,
                   "HTTP/1.0 200 OK\r\nContent-Length: %d\r\nServer: lwIP/1.4.0\r\n",
                   psend ? os_strlen(psend) : 0);

        if (psend) {
            os_sprintf(httphead + os_strlen(httphead),
                       "Content-type: text/html\r\nExpires: Fri, 10 Apr 2008 14:00:00 GMT\r\nPragma: no-cache\r\n\r\n");
            length = os_strlen(httphead) + os_strlen(psend);
            pbuf = (char *)os_zalloc(length + 1);
            os_memcpy(pbuf, httphead, os_strlen(httphead));
            os_memcpy(pbuf + os_strlen(httphead), psend, os_strlen(psend));
        } else {
            os_sprintf(httphead + os_strlen(httphead), "\n");
            length = os_strlen(httphead);
        }
    } else {
        os_sprintf(httphead, "HTTP/1.0 400 BadRequest\r\n\
Content-Length: 0\r\nServer: lwIP/1.4.0\r\n\n");
        length = os_strlen(httphead);
    }

    if (psend) {
#ifdef SERVER_SSL_ENABLE
        espconn_secure_sent(ptrespconn, pbuf, length);
#else
        espconn_sent(ptrespconn, pbuf, length);
#endif
    } else {
#ifdef SERVER_SSL_ENABLE
        espconn_secure_sent(ptrespconn, httphead, length);
#else
        espconn_sent(ptrespconn, httphead, length);
#endif
    }

    if (pbuf) {
        os_free(pbuf);
        pbuf = NULL;
    }
}

LOCAL void ICACHE_FLASH_ATTR
www_data_send_json(void *arg, bool responseOK, char *psend)
{
    uint16 length = 0;
    char *pbuf = NULL;
    char httphead[256];
    struct espconn *ptrespconn = arg;
    os_memset(httphead, 0, 256);

    if (responseOK) {
        os_sprintf(httphead,
                   "HTTP/1.0 200 OK\r\nContent-Length: %d\r\nServer: lwIP/1.4.0\r\n",
                   psend ? os_strlen(psend) : 0);

        if (psend) {
            os_sprintf(httphead + os_strlen(httphead),
                       "Content-type: application/json\r\nExpires: Fri, 10 Apr 2008 14:00:00 GMT\r\nPragma: no-cache\r\n\r\n");
            length = os_strlen(httphead) + os_strlen(psend);
            pbuf = (char *)os_zalloc(length + 1);
            os_memcpy(pbuf, httphead, os_strlen(httphead));
            os_memcpy(pbuf + os_strlen(httphead), psend, os_strlen(psend));
        } else {
            os_sprintf(httphead + os_strlen(httphead), "\n");
            length = os_strlen(httphead);
        }
    } else {
        os_sprintf(httphead, "HTTP/1.0 400 BadRequest\r\n\
Content-Length: 0\r\nServer: lwIP/1.4.0\r\n\n");
        length = os_strlen(httphead);
    }

    if (psend) {
#ifdef SERVER_SSL_ENABLE
        espconn_secure_sent(ptrespconn, pbuf, length);
#else
        espconn_sent(ptrespconn, pbuf, length);
#endif
    } else {
#ifdef SERVER_SSL_ENABLE
        espconn_secure_sent(ptrespconn, httphead, length);
#else
        espconn_sent(ptrespconn, httphead, length);
#endif
    }

    if (pbuf) {
        os_free(pbuf);
        pbuf = NULL;
    }
}

LOCAL void ICACHE_FLASH_ATTR
www_response_send(void *arg, bool responseOK)
{
    struct espconn *ptrespconn = arg;

    www_data_send(ptrespconn, responseOK, NULL);
}


LOCAL void ICACHE_FLASH_ATTR
www_parse_url(char *precv, URL_Frame *purl_frame)
{
    char *str = NULL;
    uint8 length = 0;
    char *pbuffer = NULL;
    char *pbufer = NULL;

    if (purl_frame == NULL || precv == NULL) {
        return;
    }

    pbuffer = (char *)os_strstr(precv, "Host:");

    if (pbuffer != NULL) {
        length = pbuffer - precv;
        pbufer = (char *)os_zalloc(length + 1);
        pbuffer = pbufer;
        os_memcpy(pbuffer, precv, length);
        os_memset(purl_frame->pSelect, 0, URLSize);
        os_memset(purl_frame->pCommand, 0, URLSize);
        os_memset(purl_frame->pFilename, 0, URLSize);

        if (os_strncmp(pbuffer, "GET ", 4) == 0) {
            purl_frame->Type = GET;
            pbuffer += 4;
        } else if (os_strncmp(pbuffer, "POST ", 5) == 0) {
            purl_frame->Type = POST;
            pbuffer += 5;
        }

        pbuffer ++;
        str = (char *)os_strstr(pbuffer, "HTTP");

        if (str != NULL) {
            length = str - pbuffer - 1;
            os_memcpy(purl_frame->pFilename, pbuffer, length);
        }

        os_free(pbufer);
    } else {
        return;
    }

    pbuffer = (char *)os_strstr(precv, "SSID");
    if (pbuffer != NULL) {
        purl_frame->Type = POST;
        os_memcpy(purl_frame->pCommand, "connect-wifi", strlen("connect-wifi"));
        os_free(pbufer);
    }
}


LOCAL char *buffer;
static uint32 sumlength = 0;
LOCAL bool ICACHE_FLASH_ATTR
www_save_data(char *precv, uint16 length)
{
    bool flag = false;
    char length_buf[10] = {0};
    char *ptemp = NULL;
    char *pdata = NULL;
    uint16 headlength = 0;
    static uint32 totallength = 0;

    ptemp = (char *)os_strstr(precv, "\r\n\r\n");

    if (ptemp != NULL) {
        length -= ptemp - precv;
        length -= 4;
        totallength += length;
        headlength = ptemp - precv + 4;
        pdata = (char *)os_strstr(precv, "Content-Length: ");

        if (pdata != NULL) {
            pdata += 16;
            buffer = (char *)os_strstr(pdata, "\r\n");

            if (buffer != NULL) {
                os_memcpy(length_buf, pdata, buffer - pdata);
                sumlength = atoi(length_buf);
            }
        } else {
        	if (totallength != 0x00){
        		totallength = 0;
        		sumlength = 0;
        		return false;
        	}
        }
        if ((sumlength + headlength) >= 1024) {
        	buffer = (char *)os_zalloc(headlength + 1);
            os_memcpy(buffer, precv, headlength + 1);
        } else {
        	buffer = (char *)os_zalloc(sumlength + headlength + 1);
        	os_memcpy(buffer, precv, os_strlen(precv));
        }
    } else {
        if (buffer != NULL) {
            totallength += length;
            os_memcpy(buffer + os_strlen(buffer), precv, length);
        } else {
            totallength = 0;
            sumlength = 0;
            return false;
        }
    }

    if (totallength == sumlength) {
        totallength = 0;
        sumlength = 0;
        return true;
    } else {
        return false;
    }
}
LOCAL bool ICACHE_FLASH_ATTR
www_check_data(char *precv, uint16 length)
{
        //bool flag = true;
    char length_buf[10] = {0};
    char *ptemp = NULL;
    char *pdata = NULL;
    char *tmp_precvbuffer;
    uint16 tmp_length = length;
    uint32 tmp_totallength = 0;

    ptemp = (char *)os_strstr(precv, "\r\n\r\n");

    if (ptemp != NULL) {
        tmp_length -= ptemp - precv;
        tmp_length -= 4;
        tmp_totallength += tmp_length;

        pdata = (char *)os_strstr(precv, "Content-Length: ");

        if (pdata != NULL){
            pdata += 16;
            tmp_precvbuffer = (char *)os_strstr(pdata, "\r\n");

            if (tmp_precvbuffer != NULL){
                os_memcpy(length_buf, pdata, tmp_precvbuffer - pdata);
                sumlength = atoi(length_buf);
                os_printf("A_dat:%u,tot:%u,lenght:%u\n",sumlength,tmp_totallength,tmp_length);
                if(sumlength != tmp_totallength){
                    return false;
                }
            }
        }
    }
    return true;
}


/* 功  能:将str字符串中的oldstr字符串替换为newstr字符串
 * 参  数:str:操作目标 oldstr:被替换者 newstr:替换者
 * 返回值:返回替换之后的字符串
 * 版  本: V0.2
 */
static char * ICACHE_FLASH_ATTR
strrpc(char *str,char *oldstr,char *newstr){
    char bstr[os_strlen(str)];//转换缓冲区
    int len = os_strlen(str);
    int i, len2 = 0;


    os_memset(bstr, 0, sizeof(bstr));

    for(i = 0; i < len; i++){
        if(!os_strncmp(str + i, oldstr, os_strlen(oldstr))){//查找目标字符串
        	strcat(bstr, newstr);
        	len2 = os_strlen(oldstr);
            i += len2 - 1;
        }else{
        	strncat(bstr, str + i, 1);//保存一字节进缓冲区
	    }
    }

    os_strcpy(str,bstr);
    return str;
}

static void ICACHE_FLASH_ATTR
webconfig_get_wifi_ssid_pwd(char* urlparam)
{
    char *p = NULL, *q = NULL;
    char ssid[32], pass[64];

    os_memset(ssid, 0, sizeof(ssid));
    os_memset(pass, 0, sizeof(pass));

    p = (char *)os_strstr(urlparam, "SSID=");
    q = (char *)os_strstr(urlparam, "PASSWORD=");
    if ( p == NULL || q == NULL ){
        return;
    }
    os_memcpy(ssid, p + 5, q - p - 6);
    os_memcpy(pass, q + 9, os_strlen(urlparam) - (q - urlparam) - 9);

    //空格在url参数会转码成+号,+号转码成%2B
    strrpc(ssid, "+", " ");

    os_printf("ssid[%s], pass[%s]\r\n", ssid, pass);

    wifi_set_opmode(STATIONAP_MODE);
    struct station_config stConf;
    stConf.bssid_set = 0;
    os_memset(&stConf.ssid, 0, sizeof(stConf.ssid));
    os_memset(&stConf.password, 0, sizeof(stConf.password));

    os_memcpy(&stConf.ssid, ssid, os_strlen(ssid));
    os_memcpy(&stConf.password, pass, os_strlen(pass));

    wifi_station_set_config(&stConf);

    //重启
    system_restart();
}

LOCAL void ICACHE_FLASH_ATTR
www_webserver_recv(void *arg, char *pusrdata, unsigned short length) {
	URL_Frame *pURL_Frame = NULL;
	char *pParseBuffer = NULL;
	bool parse_flag = false;
	struct espconn *ptrespconn = arg;
	uint8 sta;

	os_printf("len:%u\n", length);
	if (www_check_data(pusrdata, length) == false) {
		os_printf("goto\n");
		goto _temp_exit;
	}

	parse_flag = www_save_data(pusrdata, length);
	if (parse_flag == false) {
		www_response_send(ptrespconn, false);
	}


	//os_printf(buffer);
	pURL_Frame = (URL_Frame *) os_zalloc(sizeof(URL_Frame));
	www_parse_url(buffer, pURL_Frame);

	//os_printf("recv:%s\n", pusrdata);
	os_printf("\r\nType[%d]\r\n", pURL_Frame->Type);
	os_printf("pSelect[%s]\r\n", pURL_Frame->pSelect);
	os_printf("pCommand[%s]\r\n", pURL_Frame->pCommand);
	os_printf("pFilename[%s]\r\n", pURL_Frame->pFilename);

	switch (pURL_Frame->Type) {
		case GET:
			os_printf("We have a GET request.\n");
			os_printf("pURL_Frame->pFilename: %s\n",pURL_Frame->pFilename);

			sta = wifi_station_get_connect_status();
			if(sta == STATION_GOT_IP){

				//www_data_send(ptrespconn, true, "<meta charset='UTF-8'>WiFi is connected!");
				www_data_send(ptrespconn, true, finish_html);
			}
			else{
				if(pURL_Frame->pFilename[0] == 0){
					www_data_send(ptrespconn, true, page_html);
				}
				if(strncmp(pURL_Frame->pFilename, "WebConfig.html", strlen("WebConfig.html")) == 0){
					www_data_send(ptrespconn, true, config_html);
				}
			}

			break;

		case POST:
			os_printf("We have a POST request.\n");
			pParseBuffer = (char *)os_strstr(buffer, "\r\n\r\n");

			sta = wifi_station_get_connect_status();
			if(sta == STATION_GOT_IP){
				//www_data_send(ptrespconn, true, "<meta charset='UTF-8'>Connect Success!");
				www_data_send(ptrespconn, true, finish_html);
			}
			else if(sta == STATION_WRONG_PASSWORD){
				//www_data_send(ptrespconn, true, "<meta charset='UTF-8'>Wrong Password!");
				www_data_send(ptrespconn, true, finish_html);
			}
			else{
				www_data_send(ptrespconn, true, config_html);
			}

			if(strncmp(pURL_Frame->pCommand, "connect-wifi", strlen("connect-wifi")) == 0){
				webconfig_get_wifi_ssid_pwd(pusrdata);
			}


			if (pParseBuffer == NULL) {
			     break;

			}

			os_printf("%s\r\n", pParseBuffer);

			break;
	}

	if (buffer != NULL) {
		os_free(buffer);
		buffer = NULL;
	}
	os_free(pURL_Frame);
	pURL_Frame = NULL;
	_temp_exit: ;
}

LOCAL ICACHE_FLASH_ATTR
void www_webserver_recon(void *arg, sint8 err)
{
    struct espconn *pesp_conn = arg;

    os_printf("webserver's %d.%d.%d.%d:%d err %d reconnect\n", pesp_conn->proto.tcp->remote_ip[0],
    		pesp_conn->proto.tcp->remote_ip[1],pesp_conn->proto.tcp->remote_ip[2],
    		pesp_conn->proto.tcp->remote_ip[3],pesp_conn->proto.tcp->remote_port, err);
}

LOCAL ICACHE_FLASH_ATTR
void www_webserver_discon(void *arg)
{
    struct espconn *pesp_conn = arg;

    os_printf("webserver's %d.%d.%d.%d:%d disconnect\n", pesp_conn->proto.tcp->remote_ip[0],
        		pesp_conn->proto.tcp->remote_ip[1],pesp_conn->proto.tcp->remote_ip[2],
        		pesp_conn->proto.tcp->remote_ip[3],pesp_conn->proto.tcp->remote_port);
}


//绑定回调函数
LOCAL void ICACHE_FLASH_ATTR
www_webserver_listen(void *arg) {
	struct espconn *pesp_conn = arg;

	espconn_regist_recvcb(pesp_conn, www_webserver_recv);
	espconn_regist_reconcb(pesp_conn, www_webserver_recon);
	espconn_regist_disconcb(pesp_conn, www_webserver_discon);
}


/*
 * softAP模式初始化代码
 */
void ICACHE_FLASH_ATTR
www_softAP_init(void){
	struct softap_config soft_ap_Config;

	//wifi_set_opmode_current(SOFTAP_MODE);//设置为AP模式,不保存到flash
	wifi_set_opmode(STATIONAP_MODE);//设置为STATION+AP模式,并保存到flash

	os_memset(&soft_ap_Config, 0, sizeof(struct softap_config)); // AP参数结构体 = 0

	soft_ap_Config.ssid_len = os_strlen(AP_NAME);//热点名称长度,与你实际的名称长度一致就好
	os_strcpy(soft_ap_Config.ssid, AP_NAME);//实际热点名称设置,可以根据你的需要来
	os_strcpy(soft_ap_Config.password, AP_PWD);//热点密码设置
	soft_ap_Config.authmode = AUTH_OPEN;//若是你的密码为空,加密模式为AUTH_WPA2_PSK,那么ESP8266不会使用你设置的WIFI名称,而是系统生成,因此没有密码就把加密模式改成AUTH_OPEN
	soft_ap_Config.beacon_interval = 100;//信标间隔,默认为100
	soft_ap_Config.channel = 1;//信道,共支持1~13个信道
	soft_ap_Config.max_connection = 4;//最大连接数量,最大支持四个,默认四个
	soft_ap_Config.ssid_hidden = 0;//隐藏SSID,0:不隐藏  1:隐藏
	//wifi_softap_set_config_current(&soft_ap_Config);//设置 Wi-Fi SoftAP 接口配置,不保存到 Flash
	wifi_softap_set_config(&soft_ap_Config);//设置 Wi-Fi SoftAP 接口配置,保存到 Flash
}

//初始化webserver
void ICACHE_FLASH_ATTR
www_webserver_init(uint32 port) {
	LOCAL struct espconn esp_conn;
	LOCAL esp_tcp esptcp;

	esp_conn.type = ESPCONN_TCP;
	esp_conn.state = ESPCONN_NONE;
	esp_conn.proto.tcp = &esptcp;
	esp_conn.proto.tcp->local_port = port;
	espconn_regist_connectcb(&esp_conn, www_webserver_listen);
	espconn_accept(&esp_conn);
}

11、实现方式

在主函数中调用以下两个函数即可

//初始化AP模式
www_softAP_init();
//开启webserver
www_webserver_init(SERVER_PORT);

三、效果展示

1、连接 WiFi

在这里插入图片描述

2、配网首页

打开浏览器,地址栏输入 192.168.4.1

在这里插入图片描述

3、配网页面

在这里插入图片描述

4、配网完成页面

在这里插入图片描述

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论
ESP8266 NONOS_SDK 3.0.5 是一款针对 ESP8266 WiFi 模块的非操作系统软件开发工具包。该版本是ESP8266非操作系统软件开发工具包(NONOS_SDK)的第3.0.5版。NONOS_SDK是由乐鑫科技(Espressif Systems)提供的一套用于开发ESP8266无线芯片的开发者工具。 NONOS_SDK 3.0.5 具有以下特点和功能: 1. 硬件支持:此版本支持 ESP8266 芯片系列,包括 ESP-WROOM-02D、ESP-WROOM-02U 和 ESP-WROOM-02等。可以方便地与各种外设进行连接,如传感器、LCD 显示屏、LED 等。 2. 支持 Wi-Fi 功能:NONOS_SDK 3.0.5 支持 ESP8266 的 Wi-Fi 功能,可以实现设备与设备之间的无线通信和远程控制。开发者可以通过编写代码,使用 TCP/IP 或 UDP 协议与远程服务器进行通信。 3. 丰富的开发接口和库:该版本的 NONOS_SDK 提供了一套丰富的开发接口和库,使开发者可以方便地进行开发。开发者可以使用 C/C++ 语言编写代码,并借助提供的 API 实现各种功能,如网络通信、文件系统、定时器、GPIO 控制等。 4. 调试功能:NONOS_SDK 3.0.5 还配备了一套调试工具,帮助开发者进行调试和测试。开发者可以通过串口进行调试,并查看程序的运行状态、输出调试信息等。 使用 ESP8266 NONOS_SDK 3.0.5 进行开发,可以实现物联网等应用。开发者可以使用其中的软件接口和工具,以及丰富的开发文档,快速开发和部署自己的项目。同时,NONOS_SDK 3.0.5 也提供了不断更新的固件和驱动程序,以优化性能,提高系统的稳定性和安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菠萝蚊鸭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值