NODEMCU除錯心得7
關於網路協議 HTTP 2
上一節,我們用nodemcu伺服器向客戶端傳送nodemcu的記憶體資訊。這一節反過來,我們介紹如何用客戶端控制nodemcu。
先介紹一個簡單的例子,用客戶端控制nodemcu的GPIO4,實現nodemcu的藍色LED遠端開關。
Step 3
這裡仍然參考了 ckuehnel
的程式碼,gpio.lua
- 下面是我的程式碼,取名叫My_gpio.lua
-- SSID = " "
-- password = " "
pin = 4
-- wifi.setmode(wifi.STATION)
-- wifi.sta.config(SSID,password)
print(wifi.sta.getip())
gpio.mode(pin, gpio.OUTPUT)
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
conn:on("receive", function(conn,payload)
print(payload)
local _, _, method, path, vars = string.find(payload, "([A-Z]+) (.+)?(.+) HTTP");
if (method == nil)then
_, _, method, path = string.find(payload, "([A-Z]+) (.+) HTTP");
end
local _GET = {}
if (vars ~= nil)then
for k, v in string.gmatch(vars, "(%w+)=(%w+)&*") do
_GET[k] = v
end
end
-- HTML
local buf = "";
buf = buf.."<h1> Web Server</h1>";
buf = buf.."<p>LIGHT 1 "
buf = buf.."<a href=\"?pin=ON4\"><button>LED OFF</button></a> "
buf = buf.."<a href=\"?pin=OFF4\"><button>LED ON</button></a></p>";
if(_GET.pin == "ON4")then
gpio.write(pin, gpio.HIGH);
elseif(_GET.pin == "OFF4")then
gpio.write(pin, gpio.LOW);
end
conn:send(buf);
conn:close();
collectgarbage();
end)
end)
逐段來看程式碼
- 前兩行,定義路由器的賬號與密碼,因為我的nodemcu已經預設連上路由,所有這裡就遮蔽了。
- 注意,當你連上路由,獲得IP地址後,這個資訊就儲存在flash裡了。所以就算掉電重啟,還會自動連線
- 第三行,
pin = 4
定義GPIO,4號對應的是nodemcu上控制LED的管腳,注意高電平時LED關,低電平開。 - 第四行和第五行,設定esp8266為station模式,連線路由器。同樣之前連線過,資訊就儲存了,所以就遮蔽了。
print()
列印IP地址資訊,這個是客戶端瀏覽器要訪問nodemcu,就要輸它的IP地址。
- 每次上電時的IP有可能不一樣
gpio.mode()
設定gpio為輸出模式,也就是控制模式srv=..
建立TCP伺服器srv.listen()
監聽80埠的資訊,80也就是http協議對應的埠conn.on("recieve"..)
這個與上一節的例程一樣,也是接收客戶端發出的資訊payload
。不同之處在於:
- 上節的例程只接收客戶端連線到伺服器的資訊。
- 本節的例程除了接收連線資訊之外,還接收客戶端傳送的gpio控制指令
print()
列印客戶端發來的payload資訊,會有四五行,具體內容可以見上一節關於網路協議 HTTP
,最重要的是第一行,裡面包含的資訊需要提取出來,一般是這樣GET / HTTP/1.1
或者這樣
GET /?pin=ON4 HTTP/1.1
string.find()函式用來提取控制指令,它會返回符合規則的字串,這裡的目標資訊是客戶端傳送的控制指令,一般為如下形式:
GET /?pin=ON4 HTTP/1.1
這裡定義了3個字串
method
,path
,vars
.method
代表客戶端的方法,有GET
,POST
等等。GET
用來向伺服器傳送請求的資訊,POST
用來向伺服器提交網頁上輸入的資訊。path
代表路徑,這裡只得到一個/
vars
代表控制按鈕button
返回的控制資訊前面的兩個
_
,_
是模糊變數,這兩個變數是返回find函式找到字串的第一個和最後一個的index(沒什麼用)
find()
的第一個變數是要檢索的字串,第二個變數是檢索的規則method
,path
,vars
對應的檢索規則分別是([A-Z]+)
,(.+)
,(.+)
([A-Z]+)
,[A-Z]
代表檢索的是從A到Z的大寫字母,+
代表不是單個字母,而是多個字元構成的字串(.+)
,.
代表檢索所有字元,+
同樣代表是字串?
和HTTP
是格式,代表path
和vars
之間有?
隔開,vars
後以HTTP
結束,相當於path
和var
的讀取終止符
假如是客戶端剛連線時傳送的資訊,paylaod第一行是這樣
GET / HTTP/1.1
不符合剛才帶?的格式,
method
,path
,vars
都得到空值nil
if..
用來判斷method
是否為nil
,如果是,說明是客戶端的連線資訊,find()
的檢索模式改為([A-Z]+) (.+) HTTP
,只接收兩個字串method
和path
定義局域變數
_GET
,接收vars
中的gpio
控制資訊- 如果
vars
不為空,其代表了控制資訊 - 用for迴圈提取
pin=ON4
裡的資訊 string.gmatch()
是專用在迭代forx迴圈中的字串函式,
(%w+)=(%w+)&*
,%w
代表字母和數字,+代表是多個字元。&
應該是和=
一樣,是格式,*
的作用和+
類似,區別是*
代表該字元可以不出現。&*
的意思是vars字串的提取格式最後可以沒有&
,也可以有一個或者多個&
。
更多字串函式和模式的語法,請點選這裡和這裡
接下來是HTML程式碼
- 首先定義一個空字串
- 接下來直接寫HTML的body部分,前面沒有head,也沒有
<html>
,<head>
,<body>
關鍵字,直接寫標題1:<h1>..</h1>
- 然後寫第一端的文字部分,
<p1>
.. 接下來的程式碼定義
button
和它的返回值。關鍵字<a>
通常是連結的關鍵字,href=
後是連結的地址。但是
這裡,用來建立一個button
物件,href = \"?pin=ON4\"
:\
是轉義符,當按鈕被按下,客戶端就返回?pin=ON4
這一資訊,<button>LED OFF</button>
:LED OFF
是button物件上的文字標籤</a>
:結束<a>
開頭的程式碼, 
代表一個空格- 這行程式碼的含義是點選LED ON開啟LED,gpio4處於低電位
- 下一段程式碼的含義是:點選LED OFF關閉LED。點選後,返回
?pin=OFF4
- 文字,按鈕on和按鈕off都屬於段落p1,處在同一行上
接下來,對vars
代表的字串pin=ON4
進行判斷,
pin
作為關鍵字,ON4
或者OFF4
是關鍵字對應的元素。- 如果
_GET.pin
對應的元素是ON4
,拉高GPIO電平,反之,則拉低
最後傳送buf
包,並關閉conn
連線,清理記憶體垃圾
注意,這裡並沒有像
Step 1
的例程,沒有加入HTTP的頭部資訊,可能是傳送buff量比較小的原因,而傳送的HTML
文件本身就是一個很簡略的版本。如果傳送buff比較長,或者對傳送正確率要求比較高,還是採用標準的HTML格式和HTML協議頭部資訊。
這裡直接用
conn:close()
關閉連線,沒有像上一個例程,採用conn:on("sent"..)
事件發生函式。
Step 4
程式碼解釋完後,我們上傳到nodemcu。如果你的nodemcu沒有連上路由,或者剛刷了韌體,就取消My_gpio.lua
的註釋,輸入你的wifi賬號和密碼
- Save to ESP
後,會列印你的IP資訊,像這樣
172.21.100.79 255.255.255.0 172.21.100.254
第一個就是nodemcu的IP,後面分別是子網掩碼,和路由器的的IP
- 在瀏覽器裡輸入nodemcu的IP,出現介面,可以選擇關閉和開啟nodemcu的LED,像這樣
- 因為HTML沒有Head資訊,所以標籤上顯示的和位址列資訊是一樣的
- 點選LED OFF,原先的IP地址就會附加資訊:
/?pin=ON4
,作為客戶端的GET資訊
本來還打算講一個提交表單的例程,不過已經寫得很長了,就放到下一節好了。順帶會講一講CSS,讓沒有圖片的伺服器也能exciting。