esp8266 nodemcu lua wifi net web
闲言碎语
nodemcu的wifi模块,花了三篇文章被我水完了。内容还是比较浅显的。不过,nodemcu的开发者确实把wifi模块设计的很简单,也和容易使用。配置函数、station类函数、ap类函数、监听注册函数,总体来讲还是很清晰明了。要用熟这些wifi功能,其实还要配合其他模块一起来(比如这篇文章要说的net),循序渐进。
模块函数
net模块的函数也是比较多的。不过,整体结构也是很清晰的。赶紧来看看这些函数
序号 | 函数名 | 参数 | 返回值 |
---|---|---|---|
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 |
参数里面有个type的,只有两种选择,要么net.TCP,要么net.UDP。有几个server相关的API,几个socket相关的API。这里不打算一个一个函数的讲了,直接来几个例子反而更容易理解API的含义。
实践一下
光说不练假把式,直接来实践一下。从API中可以知道,net模块可以创建server和client。实践前,确保nodemcu已经连入网络。
- 先使用wifi.setmode()配置为STATION模式或者STATIONAP模式;
- 接着用wifi.sta.config()配置ssid和密码;
- 可能还需要使用wifi.sta.connect()来让设备连入网络。
wifi的配置后会一直生效。如果你先前配置过,可以不用配置。当然,重新配置一下也可以。
client
这里先来看看如何创建一个client,以及如何进行通信。
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创建一个net.TCP客户端,函数会返回一个socket子模块,后面要用的都是socket相关的函数,第二个参数,1表示加密,0表示不加密。net.socket:connect用来连接到服务端。参数2既可以是ip地址,也可以是域名。注意connect前面用的是冒号:,不是点。接着,找一个网络调试工具来创建一个server。这里我找了个名字叫网络调试的手机APP。net.socket:on函数用来绑定几个事件回调,函数原型是这样的 function(net.socket[, string]):
- "connection" : 连接;
- "reconnection" : 重连接;
- "disconnection" : 断开连接;
- "receive" : 接收回调,string表示接收到的字符串数据;
- "sent" : 发送;
这个例子里面,nodemcu连接到app创建的server后,并没有产生回调事件,具体是什么原因,不清楚。不过,尝试连接到域名却可以产生回调事件。比如下面这个域名
cl:connect(80, "www.nodemcu.com")
客户端
服务端
点击APP左边的客户端列表,断开nodemcu,得到一个预期的断开回调。使用.createConnection创建多个客户端。比如,这样子:
cl = net.createConnection(net.TCP, 0)
cl2 = net.createConnection(net.TCP, 0)
使用net.socket:send可以向服务端发送数据。比方说在ESPlorer右边的输入框里面输入下面这句语句:
=cl:send("Hello NodeMCU")
这里需要说明的是,send函数发送的数据长度是有限度的,大概是1400多个字节。当要发送大于1400字节的内容的时候,比如说发送一个带css、js的网页,就需要分成多次发送。多次发送也不是简单的把上面的代码复制几遍就能解决的。而是要用到"sent"事件来回调。
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)
这个例子可以让客户端在发送完第一条消息后,再发10条消息给服务端。激活的方法还是在ESPlorer中输入一条send语句。
=cl:send("Hello NodeMCU")
当nodemcu发送完第一条语句后,会触发"sent"事件,进而发送10条消息给服务端。
收到消息了!server
知道了如何创建并使用一个client后,我们来继续看如何创建一个server。先上个开胃菜。
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创建一个net.TCP的服务端,第二次参数用于设置不活动连接的超时时间,返回一个net.server模块。nodemcu只能创建一个server,不像client可以创建多个。需要注意一下。net.server只有4个函数,其中的send和on仅对udp有用。tcp要使用socket的send和on函数。
接着用net.server:listen创建一个监听。回调传入的是一个socket。可以尽情的使用socket的函数了,比如用net.socket:on设置各种事件回调。这个例子里面的"connection"依然没效果╮(╯_╰)╭。使用APP连接到创建好的server,试着发送信息。
接着到主菜上场了。内容有点长。主要是实现上篇文章说的enduser setup。动筷子前记得把wifi模式设置成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数组存储了一个web页面。当然了,这个web页面比较大,远远超过了1400字节。需要将它分成几块,以便后面分批发送。所以,把这个web页面分块存储到一个table中。
function sendWeb(c)
if #sendBuf > 0 then
s = table.remove(sendBuf, 1)
c:send(s)
else
c:close()
end
end
函数sendWeb用来把table里面的内容发送出去,一边发送,一边remove表里面的内容,所以用浏览器浏览只能打开页面一次 o(╯□╰)o。或许这个地方可以优化一下。
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)
最后这一部分,和开胃菜那个例子的效果差不多,只是这回发送的是一个页面。回调函数中,先解析浏览器get过来的内容,之后把类似于这种格式的字符串("wifi_ssid=hello&wifi_password=12345678")存储到一个table中。最后又把提取到的内容send出来,赶紧用浏览器访问nodemcu看看效果吧。只需要在浏览器的地址栏输入ip地址即可。
利用net的server,还可以显示web控制led之类的效果,网上有相关的例子。或者可以配合nodemcu上面的AD完成更多东西来。不过前提是,要能写出漂亮的web页面o(╯□╰)o。
一点lua语法
local _, _, method, path, vars = string.find(req, "([A-Z]+) (.+)?(.+) HTTP")
这个地方的 _ 实际上是一个变量,叫虚变量,因为string的find方法会返回子串的起始和结束地址。不需要的话,可以用虚变量来存储。
网友评论
c:on("receive", function(c, d)
print(d)
c:send(d)
end)
c:send(d)是为了什么,我删除貌似没什么变换