一、前言;
- 這個月也快結束了,時間真快,我服務器知識自學依然在路途中,這幾天聽到熱點網頁配置
esp8266
連接路由器,那么我想這個不是很復雜,不過需要一些通訊協議的基礎,以及對esp8266
的SDK
開發的熟悉,這幾天擼了幾下也就輕松弄出來了!不過我今天給大家帶來的是實現的原理,我是用作於gpio
口控制,也就是一盞燈的點亮點滅!當然了,你可以沿着我思路去做網頁內置配網哦!
二、整體思路;
- ①:以手機瀏覽器為例,其訪問指定的
ip
地址,過程是怎么樣的?
我們都在用手機瀏覽器,很少知道他是怎么實現訪問交互數據的。這里我們把
esp8266作為
服務器端,手機瀏覽器作為客戶端,一般地,都是get
請求,除非指定post
提交,而請求的數據格式,大家可以去百度下http
協議的數據格式,這里不再累贅!而請求之后,esp8266
那肯定是要以http
協議數據來回復內容的,這內容也就包含了gpio
的管腳狀態!從而實現了數據交互!
- ②:編寫好的
html
對應燒錄的地址,應該怎么注意什么?
這里我就不再多說
html
的文件怎么編寫,這需要一定的前端知識。對應的燒錄地址必須在代碼塊外的地址燒錄,大家不懂哪些是代碼塊外的地址,可以去看看我上個月寫的25q16
存儲芯片的分布,點我查看!,之后我們需要在代碼中讀取這個網頁,之后發送給客戶端就可以了!
三、編寫一個簡單的Html
文件;
- 非常簡單,我這里直接上代碼:
- 用的是
post
提交,不是get
請求! - 當點擊開燈,發送
powerOn=1
,點擊關燈發送powerOn=0
! - 注意編碼是
utf-8
!
- 用的是
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"></meta><title>esp8266內置網頁單開關燈</title></head>
<body>
<h2 align="center">esp8266熱點內置網頁單開關燈By半顆心臟</h2>
<h3 align="center">%s</h3>
<form method="post"action="setLight">
<table align="center"><tr><td>開燈:</td><td>
<button name="powerOn"type="submit"value="1">點我開燈</button>
</td></tr><tr><td>關燈:</td><td>
<button name="powerOff"type="submit"value="0">點我關燈</button>
</td></tr>
</table>
</form>
</body>
</html>
用電腦瀏覽器打開預覽如下:
四、esp8266
編程;
看標題大家都知道,這個設備
esp8266
是作為一個熱點讓客戶端去主動連接,那么esp8266
必須要開啟熱點模式,我這里讓它固定一個ip
地址192.168.5.1
,當然了,你也可以不設置固定地址,因為默認就是192.168.4.1
!開啟熱點之后,等待客戶端連接,如果客戶端有成功連接后,開啟tcp
服務器(其實就是web服務器第一步
),這時候就是一直處於和客戶端連接交互數據的狀態了!
4.1 配置熱點模式,開啟軟路由!
下面代碼中的
webEsp8266
是設備發出的熱點名字,xh12345678
是密碼,192, 168, 5, 1
是固定自定義的ip
地址,允許最大四個的客戶端連接,而且分配的ip
是從192, 168, 5, 100
到192, 168, 5, 105
;
wifi_set_opmode(SOFTAP_MODE);
struct softap_config *config = (struct softap_config *) zalloc(
sizeof(struct softap_config)); // 初始化
wifi_softap_get_config(config);
sprintf(config->ssid, "webEsp8266");
sprintf(config->password, "xh12345678");
config->authmode = AUTH_WPA_WPA2_PSK;
config->ssid_len = 0;
config->max_connection = 4;
wifi_softap_set_config(config); // Set ESP8266 soft-AP config
free(config);
struct station_info * station = wifi_softap_get_station_info();
while (station) {
printf("bssid : MACSTR, ip : IPSTR/n", MAC2STR(station->bssid),
IP2STR(&station->ip));
station = STAILQ_NEXT(station, next);
}
wifi_softap_free_station_info(); // Free it by calling functionss
wifi_softap_dhcps_stop(); // disable soft-AP DHCP server
//配置dhcp,固定esp8266的ip為 192, 168, 5, 1
struct ip_info info;
IP4_ADDR(&info.ip, 192, 168, 5, 1);
IP4_ADDR(&info.gw, 192, 168, 5, 1);
IP4_ADDR(&info.netmask, 255, 255, 255, 0);
wifi_set_ip_info(SOFTAP_IF, &info);
struct dhcps_lease dhcp_lease;
IP4_ADDR(&dhcp_lease.start_ip, 192, 168, 5, 100); //分配的網段ip開始
IP4_ADDR(&dhcp_lease.end_ip, 192, 168, 5, 105); //分配的網段ip結束
wifi_softap_set_dhcps_lease(&dhcp_lease);
wifi_softap_dhcps_start(); // 使能 soft-AP DHCP 服務
4.2 創建tcp
服務器!
代碼比較復雜,總的來說,先初始化
socket
,之后bind
綁定端口號,大家都知道瀏覽器的默認訪問的端口是80
,那么這里也肯定是80
,然后監聽這個端口,阻塞等待消息!
int32 listenfd;
int32 ret = 0;
char input[1024] = { 0 };
char output[1024] = { 0 };
struct sockaddr_in server_addr, remote_addr;
int stack_counter = 0;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_len = sizeof(server_addr);
server_addr.sin_port = htons(80);
printf("[XHLogUtils] Task_local_server init succeed!!! \n");
/* Create socket for incoming connections */
do {
listenfd = socket(AF_INET, SOCK_STREAM, 0);
printf("[XHLogUtils] Create socket for incoming connections !!! \n");
if (listenfd == -1) {
printf(
"[XHLogUtils] Create socket for incoming connections -1 !!! \n");
vTaskDelay(1000 / portTICK_RATE_MS);
}
} while (listenfd == -1);
/* Bind to the local port */
do {
ret = bind(listenfd, (struct sockaddr * )&server_addr,
sizeof(server_addr));
printf("[XHLogUtils] Create socket binding !!! \n");
if (ret != 0) {
printf("Create socket binding = -1 \n");
vTaskDelay(1000 / portTICK_RATE_MS);
}
} while (ret != 0);
do {
// Listen to the local connection
ret = listen(listenfd, 4);
printf("[XHLogUtils] Create socket listening !!! \n");
if (ret != 0) {
printf(
"[XHLogUtils] Create socket listening = -1 will close!!! \n");
vTaskDelay(1000 / portTICK_RATE_MS);
}
} while (ret != 0);
int32 client_sock;
int32 len = sizeof(struct sockaddr_in);
for (;;) {
printf(
"[XHLogUtils] Task_local_server block here waiting remote connect request !!! \n");
/*block here waiting remote connect request*/
if ((client_sock = accept(listenfd, (struct sockaddr * )&remote_addr,
(socklen_t * )&len)) < 0) {
printf("[XHLogUtils] acceptting < 0...\n");
continue;
} else {
printf("[XHLogUtils] acceptting > 0...\n");
}
}
4.3 對來自客戶端數據的處理以及回復!
前面已經說了,我們點擊開關燈時候,是
post
提交數據,所以我們是先判斷否為post
提交,然后對里面的數據進一步剖析,我們看看客戶端發來了什么內容?對比下面可以看到,在body
里面數據不一樣,開燈時候是powerOn=1
,而關燈是powerOn=0
!那么我們就從body
里面數據剖析就可以啦?這豈不是很簡單?
- 開燈請求得到客戶端數據:
POST /setLight HTTP/1.1
Host: 192.168.5.1
Connection: keep-alive
Content-Length: 9
Cache-Control: max-age=0
Origin: http://192.168.5.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; MI 8 Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/8.9 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,image/sharpp,image/apng,image/tpg,*/*;q=0.8
Referer: http://192.168.5.1/setLight
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,en-US;q=0.8
powerOn=1
- 關燈請求得到客戶端數據:
POST /setLight HTTP/1.1
Host: 192.168.5.1
Connection: keep-alive
Content-Length: 9
Cache-Control: max-age=0
Origin: http://192.168.5.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; MI 8 Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/8.9 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,image/sharpp,image/apng,image/tpg,*/*;q=0.8
Referer: http://192.168.5.1/setLight
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,en-US;q=0.8
powerOn=0
- 對
body
里面數據剖析,進一步得到指定的動作執行gpio
!!並回復給客戶端,注意之后主動要斷開tcp
連接!
//確定是post請求
if (input[0] == 'P' && input[1] == 'O' && input[2] == 'S'
&& input[3] == 'T') {
//顯示client 端的網絡地址
char *pBody = NULL;
//得到body
get_http_body(input, &pBody);
char attribute[] = { "" };
//截取之后保存的位置,源字符串,要截取的字符串的長度
strncpy(attribute, pBody, strlen(pBody) - 2);
//獲取value設置數值
char *pValue = (char *) strstr(pBody, "=");
pValue += 1;
if (strcmp(pValue, "0") == 0) {
GPIO_OUTPUT_SET(GPIO_ID_PIN(12), 1);
} else {
GPIO_OUTPUT_SET(GPIO_ID_PIN(12), 0);
}
}
char *pStatus;
if (GPIO_INPUT_GET(12) == 0x00) {
pStatus = "智能燈的當前狀態:開";
} else {
pStatus = "智能燈的當前狀態:關";
}
char tempHttpHead[1024], tempHttpBody[1024];
sprintf(tempHttpHead, httpHead, strlen(tempSaveData));
//協議頭拼接到發送的變量
sprintf(sendstr, tempHttpHead);
//設置結束符
tempSaveData[594] = 0;
//協議body拼接到發送的變量
sprintf(tempHttpBody, tempSaveData, pStatus);
//拼接到發送全部消息
strcat(sendstr, tempHttpBody);
write(client_sock, sendstr, strlen(sendstr));
五、esp8266
的flash
讀取網頁的注意要點;
- 在我之前說到的是先通過工具把
html
網頁燒錄到flash
芯片,我這里使用的是25q32
,可用的空間會比較大,我這里就燒錄到0x1F4000
,計算之后是哪個扇區呢?大家可以算下,0x1F4000
換算十進制就是2048000
,一個扇區是4096 bytes
,而2048000 / 4096 = 500
,也就是第 500 個扇區了!於是我們代碼這樣讀取:
//500*4096 相當於 0x1F4000 ,也就是 0x1F4 * 4096
spi_flash_read(500 * 4096, (uint32 *) &tempSaveData, sizeof(tempSaveData));
printf("get Html Content: %s \n", tempSaveData);
- 在拿到了網頁信息之后,要自己設置字符串內容的結束符,這就需要我們的
Html
文件有多大?注意:我們要的是顯示全部內容下的時候才拿到這個Html
文件大小,注意我們上面的是格式符%s
,取出來的當然會小很多!
//設置結束符
tempSaveData[594] = 0;
六、其他注意要點;
- 上面注意這個文件大小,再來設置結束符!如果設置不對,設置過多或過少,會影響顯示效果哦!切記切記!
- 下面是燒錄固件和
Html
文件的燒錄截圖!
- 下面是客戶端手機瀏覽器截圖!
-
固件下載,非源碼:https://blog.csdn.net/xh870189248/article/details/83543997#_347
-
esp8266源代碼學習匯總(持續更新,歡迎star):https://github.com/xuhongv/StudyInEsp8266
-
esp32源代碼學習匯總(持續更新,歡迎star):https://github.com/xuhongv/StudyInEsp32
esp8266燒錄Html文件,實現內置網頁控制設備!
注:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權