esp8266??nodemcu??lua??wifi??net??web
閑言碎語(yǔ)
nodemcu的wifi模塊瞒御,花了三篇文章被我水完了。內(nèi)容還是比較淺顯的神郊。不過(guò)肴裙,nodemcu的開(kāi)發(fā)者確實(shí)把wifi模塊設(shè)計(jì)的很簡(jiǎn)單,也和容易使用涌乳。配置函數(shù)蜻懦、station類函數(shù)、ap類函數(shù)夕晓、監(jiān)聽(tīng)注冊(cè)函數(shù)宛乃,總體來(lái)講還是很清晰明了。要用熟這些wifi功能蒸辆,其實(shí)還要配合其他模塊一起來(lái)(比如這篇文章要說(shuō)的net)征炼,循序漸進(jìn)。
這里有個(gè)綜合文章這里
模塊函數(shù)
net模塊的函數(shù)也是比較多的躬贡。不過(guò)谆奥,整體結(jié)構(gòu)也是很清晰的。趕緊來(lái)看看這些函數(shù)
序號(hào) | 函數(shù)名 | 參數(shù) | 返回值 |
---|---|---|---|
1 | net.createConnection() | type, secure | net.socket 子模塊 |
2 | net.createServer() | type, timeout | net.server 子模塊 |
3 | net.multicastJoin() | if_ip, multicast_ip | 空 |
4 | net.multicastLeave() | if_ip, multicast_ip | 空 |
5 | net.server:close() | 空 | 空 |
6 | net.server:listen() | port,[ip],function(net.socket) | 空 |
7 | net.server:on() | ||
8 | net.server:send() | ||
9 | net.socket:close() | 空 | nil |
10 | net.socket:connect() | port, ip / domain | nil |
11 | net.socket:dns() | domain, function(net.socket, ip) | 空 |
12 | net.socket:getpeer() | 空 | ip, port |
13 | net.socket:hold() | 空 | 空 |
14 | net.socket:on() | event, function() | nil |
15 | net.socket:send() | string[, function(sent)] | 空 |
16 | net.socket:unhold() | 空 | 空 |
17 | net.dns.getdnsserver() | dns_index(0 / 1) | ip |
18 | net.dns.resolve() | host, function(ip) | nil |
19 | net.dns.setdnsserver() | dns_ip_addr, dns_index | nil |
20 | net.cert.verify() | enable / pemdata | true |
參數(shù)里面有個(gè)type的拂玻,只有兩種選擇酸些,要么net.TCP宰译,要么net.UDP。有幾個(gè)server相關(guān)的API魄懂,幾個(gè)socket相關(guān)的API沿侈。這里不打算一個(gè)一個(gè)函數(shù)的講了,直接來(lái)幾個(gè)例子反而更容易理解API的含義市栗。
實(shí)踐一下
光說(shuō)不練假把式缀拭,直接來(lái)實(shí)踐一下。從API中可以知道肃廓,net模塊可以創(chuàng)建server和client智厌。實(shí)踐前,確保nodemcu已經(jīng)連入網(wǎng)絡(luò)盲赊。
- 先使用wifi.setmode()配置為STATION模式或者STATIONAP模式铣鹏;
- 接著用wifi.sta.config()配置ssid和密碼;
- 可能還需要使用wifi.sta.connect()來(lái)讓設(shè)備連入網(wǎng)絡(luò)哀蘑。
wifi的配置后會(huì)一直生效诚卸。如果你先前配置過(guò),可以不用配置绘迁。當(dāng)然合溺,重新配置一下也可以。
client
這里先來(lái)看看如何創(chuàng)建一個(gè)client缀台,以及如何進(jìn)行通信棠赛。
cl = net.createConnection(net.TCP, 0)
cl:connect(9999, "192.168.199.101")
cl:on("receive", function(sck, c) print(c) end)
cl:on("disconnection", function(sck, c) print("disconnection!") end)
使用.createConnection創(chuàng)建一個(gè)net.TCP客戶端,函數(shù)會(huì)返回一個(gè)socket子模塊膛腐,后面要用的都是socket相關(guān)的函數(shù)睛约,第二個(gè)參數(shù),1表示加密哲身,0表示不加密辩涝。net.socket:connect用來(lái)連接到服務(wù)端。參數(shù)2既可以是ip地址勘天,也可以是域名怔揩。注意connect前面用的是冒號(hào):,不是點(diǎn)脯丝。接著商膊,找一個(gè)網(wǎng)絡(luò)調(diào)試工具來(lái)創(chuàng)建一個(gè)server。這里我找了個(gè)名字叫網(wǎng)絡(luò)調(diào)試的手機(jī)APP巾钉。net.socket:on函數(shù)用來(lái)綁定幾個(gè)事件回調(diào)翘狱,函數(shù)原型是這樣的 function(net.socket[, string]):
- "connection" : 連接;
- "reconnection" : 重連接砰苍;
- "disconnection" : 斷開(kāi)連接潦匈;
- "receive" : 接收回調(diào)阱高,string表示接收到的字符串?dāng)?shù)據(jù);
- "sent" : 發(fā)送茬缩;
這個(gè)例子里面赤惊,nodemcu連接到app創(chuàng)建的server后,并沒(méi)有產(chǎn)生回調(diào)事件凰锡,具體是什么原因未舟,不清楚。不過(guò)掂为,嘗試連接到域名卻可以產(chǎn)生回調(diào)事件裕膀。比如下面這個(gè)域名
cl:connect(80, "www.nodemcu.com")
點(diǎn)擊APP左邊的客戶端列表,斷開(kāi)nodemcu勇哗,得到一個(gè)預(yù)期的斷開(kāi)回調(diào)昼扛。使用.createConnection創(chuàng)建多個(gè)客戶端。比如欲诺,這樣子:
cl = net.createConnection(net.TCP, 0)
cl2 = net.createConnection(net.TCP, 0)
使用net.socket:send可以向服務(wù)端發(fā)送數(shù)據(jù)抄谐。比方說(shuō)在ESPlorer右邊的輸入框里面輸入下面這句語(yǔ)句:
=cl:send("Hello NodeMCU")
這里需要說(shuō)明的是,send函數(shù)發(fā)送的數(shù)據(jù)長(zhǎng)度是有限度的扰法,大概是1400多個(gè)字節(jié)蛹含。當(dāng)要發(fā)送大于1400字節(jié)的內(nèi)容的時(shí)候,比如說(shuō)發(fā)送一個(gè)帶css塞颁、js的網(wǎng)頁(yè)浦箱,就需要分成多次發(fā)送。多次發(fā)送也不是簡(jiǎn)單的把上面的代碼復(fù)制幾遍就能解決的祠锣。而是要用到"sent"事件來(lái)回調(diào)憎茂。
cnt = 0
cl = net.createConnection(net.TCP, 0)
cl:connect(9999, "192.168.199.101")
cl:on("receive", function(sck, c) print(c) end)
cl:on("disconnection", function(sck, c) print("disconnection!") end)
cl:on("sent", function(c)
if cnt ~= 10 then
cl:send(cnt)
cnt = cnt + 1
end
end)
這個(gè)例子可以讓客戶端在發(fā)送完第一條消息后,再發(fā)10條消息給服務(wù)端锤岸。激活的方法還是在ESPlorer中輸入一條send語(yǔ)句。
=cl:send("Hello NodeMCU")
當(dāng)nodemcu發(fā)送完第一條語(yǔ)句后板乙,會(huì)觸發(fā)"sent"事件是偷,進(jìn)而發(fā)送10條消息給服務(wù)端。
server
知道了如何創(chuàng)建并使用一個(gè)client后蛋铆,我們來(lái)繼續(xù)看如何創(chuàng)建一個(gè)server。先上個(gè)開(kāi)胃菜放接。
ns = net.createServer(net.TCP, 15)
ns:listen(80, function(c)
c:on("receive", function(c, d)
print(d)
c:send(d)
end)
c:on("connection", function(c, d) print(d) end)
c:on("disconnection", function(c, d) print("disconnection") end)
end)
使用.createServer創(chuàng)建一個(gè)net.TCP的服務(wù)端刺啦,第二次參數(shù)用于設(shè)置不活動(dòng)連接的超時(shí)時(shí)間,返回一個(gè)net.server模塊纠脾。nodemcu只能創(chuàng)建一個(gè)server玛瘸,不像client可以創(chuàng)建多個(gè)蜕青。需要注意一下。net.server只有4個(gè)函數(shù)糊渊,其中的send和on僅對(duì)udp有用右核。tcp要使用socket的send和on函數(shù)。
??接著用net.server:listen創(chuàng)建一個(gè)監(jiān)聽(tīng)渺绒『睾龋回調(diào)傳入的是一個(gè)socket∽诩妫可以盡情的使用socket的函數(shù)了躏鱼,比如用net.socket:on設(shè)置各種事件回調(diào)。這個(gè)例子里面的"connection"依然沒(méi)效果╮(╯_╰)╭殷绍。使用APP連接到創(chuàng)建好的server染苛,試著發(fā)送信息。
??接著到主菜上場(chǎng)了篡帕。內(nèi)容有點(diǎn)長(zhǎng)殖侵。主要是實(shí)現(xiàn)上篇文章說(shuō)的enduser setup。動(dòng)筷子前記得把wifi模式設(shè)置成AP模式或者混合模式镰烧。
web = '<!doctype html><html><head><meta charset=\'utf-8\'><meta name=\'viewport\'content=\'width=380\'><title>Connect gadget to you WiFi</title><style media=\'screen\'type=\'text/css\'>*{margin:0;padding:0}html{height:100%;background:linear-gradient(rgba(196,102,0,0.2),rgba(155,89,182,0.2)),url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAA8AgMAAACm+SSwAAAADFBMVEVBR1FFS1VHTlg8Q0zU/YXIAAADVElEQVQ4yy1TTYvTUBQ9GTKiYNoodsCF4MK6U4TZChOhiguFWHyBFzqlLl4hoeNvEBeCrlrhBVKq1EUKLTP+hvi1GyguXqBdiZCBzGqg20K8L3hDQnK55+OeJNguHx6UujYl3dL5ALn4JOIUluAqeAWciyGaSdvngOWzNT+G0UyGUOxVOAdqkjXDCbBiUyjZ5QzYEbGadYAi6kHxth+kthXNVNCDofwhGv1D4QGGiM9iAjbCHgr2iUUpDJbs+VPQ4xAr2fX7KXbkOJMdok965Ksb+6lrjdkem8AshIuHm9Nyu19uTunYlOXDTQqi8VgeH0kBXH2xq/ouiMZPzuMukymutrBmulUTovC6HqNFW2ZOiqlpSXZOTvSUeUPxChjxol8BLbRy4gJuhV7OR4LRVBs3WQ9VVAU7SXgK2HeUrOj7bC8YsUgr3lEV/TXB7hK90EBnxaeg1Ov15bY80M736ekCGesGAaGvG0Ct4WRkVQVHIgIM9xJgvSFfPay8Q6GNv7VpR7xUnkvhnMQCJDYkYOtNLihV70tCU1Sk+BQrpoP+HLHUrJkuta40C6LP5GvBv+Hqo10ATxxFrTPvNdPr7XwgQud6RvQN/sXjBGzqbU27wcj9cgsyvSTrpyXV8gKpXeNJU3aFl7MOdldzV4+HfO19jBa5f2IjWwx1OLHIvFHkqbBj20ro1g7nDfY1DpScvDRUNARgjMMVO0zoMjKxJ6uWCPP+YRAWbGoaN8kXYHmLjB9FXLGOazfFVCvOgqzfnicNPrHtPKlex2ye824gMza0cTZ2sS2Xm7Qst/UfFw8O6vVtmUKxZy9xFgzMys5cJ5fxZw4y37Ufk1Dsfb8MqOjYxE3ZMWxiDcO0PYUaD2ys+8OW1pbB7/e3sfZeGVCL0Q2aMjjPdm2sxADuejZxHJAd8dO9DSUdA0V8/NggRRanDkBrANn8yHlEQOn/MmwoQfQF7xgmKDnv520bS/pgylP67vf3y2V5sCwfoCEMkZClgOfJAFX9eXefR2RpnmRs4CDVPceaRfoFzCkJVJX27vWZnoqyvmtXU3+dW1EIXIu8Qg5Qta4Zlv7drUCoWe8/8MXzaEwux7ESE9h6qnHj3mIO0/D9RvzfxPmjWiQ1vbeSk4rrHwhAre35EEVaAAAAAElFTkSuQmCC)}body{font-family:arial,verdana}div{position:absolute;margin:auto;top:0;right:0;bottom:0;left:0;width:320px;height:274px}form{width:320px;text-align:center;position:relative}form fieldset{background:white;border:0 none;border-radius:5px;box-shadow:0 0 15px 1px rgba(0,0,0,0.4);padding:20px 30px;box-sizing:border-box}form input{padding:15px;border:1px solid#ccc;border-radius:3px;margin-bottom:10px;width:100%;box-sizing:border-box;font-family:montserrat;color:#2C3E50;font-size:13px}form.action-button{width:100px;background:#27AE60;font-weight:bold;color:white;border:0 none;border-radius:3px;cursor:pointer;padding:10px 5px;margin:10px 5px}form.action-button:hover,#msform.action-button:focus{box-shadow:0 0 0 2px white,0 0 0 3px#27AE60}.fs-title{font-size:15px;text-transform:uppercase;color:#2C3E50;margin-bottom:10px}.fs-subtitle{font-weight:normal;font-size:13px;color:#666;margin-bottom:20px}</style></head><body><div><form><fieldset><h2 class=\'fs-title\'>WiFi Login</h2><h3 class=\'fs-subtitle\'>Connect gadget to your WiFi</h3><input type=\'text\'autocorrect=\'off\'autocapitalize=\'none\'name=\'wifi_ssid\'placeholder=\'WiFi Name\'/><input type=\'password\'name=\'wifi_password\'placeholder=\'Password\'/><input type=\'submit\'name=\'save\'class=\'submit action-button\'value=\'Save\'/></fieldset></form></div></body></html>'
sendBuf = {}
for i = 1, #web, 1400 do
local len = #web - i
if len > 1400 then
sendBuf[#sendBuf + 1] = string.sub(web, i, i+1400-1)
else
sendBuf[#sendBuf + 1] = string.sub(web, i, i+len)
end
end
web數(shù)組存儲(chǔ)了一個(gè)web頁(yè)面拢军。當(dāng)然了,這個(gè)web頁(yè)面比較大怔鳖,遠(yuǎn)遠(yuǎn)超過(guò)了1400字節(jié)茉唉。需要將它分成幾塊,以便后面分批發(fā)送结执。所以度陆,把這個(gè)web頁(yè)面分塊存儲(chǔ)到一個(gè)table中。
function sendWeb(c)
if #sendBuf > 0 then
s = table.remove(sendBuf, 1)
c:send(s)
else
c:close()
end
end
函數(shù)sendWeb用來(lái)把table里面的內(nèi)容發(fā)送出去献幔,一邊發(fā)送懂傀,一邊remove表里面的內(nèi)容,所以用瀏覽器瀏覽只能打開(kāi)頁(yè)面一次 o(╯□╰)o蜡感〉乓希或許這個(gè)地方可以優(yōu)化一下。
sv = net.createServer(net.TCP, 60)
sv:listen(80, function(c)
c:on("receive", function(cn, req)
local _, _, method, path, vars = string.find(req, "([A-Z]+) (.+)?(.+) HTTP")
if method == nil then
_, _, method, path = string.find(req, "([A-Z]+) (.+) HTTP")
end
local _GET = {}
if vars ~= nil then
for k, v in string.gmatch(vars, "(%w+_%w+)=(%w+)&*") do
_GET[k] = v
print(k .. ":" .. v)
end
local sendbuf = "<h1>Config Succeed!</h1>"
sendbuf = sendbuf.."<p>wifi_ssid: ".._GET["wifi_ssid"].."</P>"
sendbuf = sendbuf.."<p>wifi_password :".._GET["wifi_password"].."</P>"
cn:send(sendbuf)
cn:close()
else
cn:on("sent", sendWeb)
sendWeb(cn)
end
end)
end)
最后這一部分郑兴,和開(kāi)胃菜那個(gè)例子的效果差不多犀斋,只是這回發(fā)送的是一個(gè)頁(yè)面∏榱回調(diào)函數(shù)中叽粹,先解析瀏覽器get過(guò)來(lái)的內(nèi)容,之后把類似于這種格式的字符串("wifi_ssid=hello&wifi_password=12345678")存儲(chǔ)到一個(gè)table中。最后又把提取到的內(nèi)容send出來(lái)虫几,趕緊用瀏覽器訪問(wèn)nodemcu看看效果吧锤灿。只需要在瀏覽器的地址欄輸入ip地址即可。
??利用net的server持钉,還可以顯示web控制led之類的效果衡招,網(wǎng)上有相關(guān)的例子∶壳浚或者可以配合nodemcu上面的AD完成更多東西來(lái)始腾。不過(guò)前提是,要能寫出漂亮的web頁(yè)面o(╯□╰)o空执。
一點(diǎn)lua語(yǔ)法
local _, _, method, path, vars = string.find(req, "([A-Z]+) (.+)?(.+) HTTP")
這個(gè)地方的 _ 實(shí)際上是一個(gè)變量浪箭,叫虛變量,因?yàn)閟tring的find方法會(huì)返回子串的起始和結(jié)束地址辨绊。不需要的話奶栖,可以用虛變量來(lái)存儲(chǔ)。
簡(jiǎn)書評(píng)論不能貼圖, 如有需要可以到我的GitHub上提issues