我们撰写了一系列关于连接到“物联网”(Internet of Things,IoT)以及与“物联网”配合工作的文章,这些文章难度较大,本文是其中的第二部分。第一部分文章在这里。这个项目相对不是很复杂,但是仍然需要使用安装Linux系统的终端。在尝试这个项目前,我们建议你最好已经拥有使用Linux和Lua脚本语言的经验。这里是关于Lua脚本语言的文档,而这里是关于NodeMCU API的文档。请记住,文档是你的好朋友!
上一次,我们讨论了如何使用采用了开源工具和源文件来定制和编译NodeMCU固件。假设我们已经安装好一个可以正常工作的NodeMCU固件,我们现在要探索该设备的真正功能,开始与“物联网”中的“网”以及“物”展开交互。首先,我们刷新NodeMCU固件,这个项目将通过Screen连接到设备,同时使用内置的Lua解释器以建立文件系统并连接到网络上。接下来,我们使用Luatool自动化脚本执行进程,同时使用普通的TCP服务器将所有内容整合到一起,且这个TCP服务器可以与脉宽调制LED、电位器以及开关交互。
· 运行Linux系统的电脑 (Ubuntu 15.04 Vivid Vervet)
· NodeMCU Devkit v0.9,且包含如下用户模组:Node, File, GPIO, WiFi, Net, PWM, Timer, ADC, UART
· 可使用的WiFi
· 1个LED灯
· 1个100-300欧姆的电阻器
· 1个5K 电阻器
· 1个10K电位器
· 1个正常使用的瞬时开关
软件:
· Git
· Python 2.7
· Pyserial
· Linux Screen
· Luatool
要在Ubuntu 15.04上安装这些软件,请在终端上键入以下内容:
sudo apt-get install git screen python2.7 python-serial
mkdir -p ~/.opt && cd ~/.opt # just a user-owned directory for software. Replace with what suits your needs
git clone https://github.com/4refr0nt/luatool.git
1) 连接到NodeMCU
所有与NodeMCU之间的交互都是通过波特率为9600的串口实现的。我建议使用Screen终端,你也可以使用Minicom或者其它任何能够与串口交互的程序。插入NodeMCU Devkit,然后输入:
screen /dev/ttyUSB0 9600
如果NodeMCU Devkit没有挂载到/dev/ttyUSB0,请使用dmesg | grep tty
找到它。
这将显示一个普通的黑色终端屏幕,且没有文字。一旦连接成功,按下带有USER标志的按钮,重新启动设备。或者在终端中键入以下内容,并按下回车键:
git clone https://github.com/4refr0nt/luatool.git
node.restart()
按下这里的小按钮
这个时候你应该有点儿糊涂了(不同波特率的通信),然后,欢迎信息和“>”符号出现了。现在,你已经进入了NodeMCU的Lua解释器。由于这是一次全新的安装,而且我们也尚未向设备发送任何脚本,因此Init.lua这个错误的出现在我们的意料之中。一旦启动,NodeMCU 就会运行Init.lua(类似于Arduino中的setup()函数)。
解释器已经位于电脑中了?!就是这么简单!
2) 格式化节点文件系统
如果你的NodeMCU是新安装好的,在开始编写和保存Lua脚本文件之前,我们需要格式化NodeMCU的文件系统。在解释器中键入:
File.format()
一旦完成,你可以通过使用调用file.fsinfo()查看文件系统上的信息:
remaining, used, total = file.fsinfo()
print("\nFile system info:\nTotal: "..total.." Bytes\nUsed: "..used.." Bytes\nRemaining: "..remaining.." Bytes\n")
这只会显示出 NodeMCU 文件系统的总大小、剩余空间以及已使用空间,而不会显示 ESP8266 的内存信息。ESP8266拥有64KiB的指令内存,96KiB的数据内存以及大约4MiB的外部闪存。
剩余3.5MB?已经足够我们使用了。
注意:如果在键入指令时,你看到了类似于“stdin:1: unexpected symbol near ‘char(27)’” 或者“stdin: bad header in precompiled chunk” 的内容的话,请不必担心:这只是Screen出现的小问题。Hannes Lehmann 在他自己的网站上写到了这个问题,他说:“如果你遇到了下面的错误,比如‘stdin:1: unexpected symbol near ‘char(27)’ 或者 ‘stdin: bad header in precompiled chunk’,而且你的终端也无法实现后退,而且也无法正常使用箭头键(或者是复制&粘贴,或者是你已经修改了要输入的内容),不要担心,重新输入指令即可。”
我对Screen进行了专门设置,因此Screen似乎可以使用后退键进行更正,但是当我尝试使用箭头键的时候,Screen遇到了麻烦。
3) 连接到无线网
由于ESP8266的主要卖点是它的WiFi协议栈,现在我们使用Lua 解释器连接到局域网并获得一个IP地址。
设备上可交互的Lua终端非常适合简单的代码。要连接到你的无线局域网并显示IP信息,请向终端中键入:
wifi.setmode(wifi.STATION)
wifi.sta.config("wifi_name","wifi_pass") -- Replace these two args with your own network
ip, nm, gw=wifi.sta.getip()
print("\nIP Info:\nIP Address: "..ip.." \nNetmask: "..nm.." \nGateway Addr: "..gw.."\n")
连接成功!
4) 用 Luatool 实现自动化
测试简单的代码感觉非常棒,但是如果你想编写一些更复杂的东西而且在启动时就自动运行的话,那该怎么做呢?Github用户 4refr0nt 编写了一个名为 Luatool的程序,该程序可以从你的电脑上把Lua脚本上传到NodeMCU Devkit,而且可以在设备的文件系统上保存脚本。导航至你一开始从Github中复制的Luatool文件夹:
cd ~/.opt/luatool/luatool
除了luatool.py: init.lua和main.lua,文件夹中应该还包括另外两份文件。使用你比较喜欢的编辑器,把不同的文件修改成下面这个样子:
-- init.lua --
-- Global Variables (Modify for your network)
ssid = "my_ssid"
pass = "my_pass"
-- Configure Wireless Internet
print('\nAll About Circuits init.lua\n')
wifi.setmode(wifi.STATION)
print('set mode=STATION (mode='..wifi.getmode()..')\n')
print('MAC Address: ',wifi.sta.getmac())
print('Chip ID: ',node.chipid())
print('Heap Size: ',node.heap(),'\n')
-- wifi config start
wifi.sta.config(ssid,pass)
-- wifi config end
-- Run the main file
dofile("main.lua")
-- main.lua --
-- Connect
print('\nAll About Circuits main.lua\n')
tmr.alarm(0, 1000, 1, function()
if wifi.sta.getip() == nil then
print("Connecting to AP...\n")
else
ip, nm, gw=wifi.sta.getip()
print("IP Info: \nIP Address: ",ip)
print("Netmask: ",nm)
print("Gateway Addr: ",gw,'\n')
tmr.stop(0)
end
end)
-- Start a simple http server
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
conn:on("receive",function(conn,payload)
print(payload)
conn:send(" Hello, NodeMCU!!!
")
end)
conn:on("sent",function(conn) conn:close() end)
end)
5) 关闭当前的Screen会话(否则 Luatool 将无法与 NodeMCU 通信),然后将两个文件都上传至NodeMCU;
python luatool.py --port /dev/ttyUSB0 --src init.lua --dest init.lua --verbose
python luatool.py --port /dev/ttyUSB0 --src main.lua --dest main.lua --verbose
6) 使用Screen重新连接到NodeMCU Devkit,按下USER按钮以重新设置设备:
screen /dev/ttyUSB0 9600
然后你会看到如下内容:
访问这个IP地址(我的是192.168.168.10),好了,搞定!
检查一下吧!现在我们有了一个很小的服务器!
7) 搭建电路,上传服务器代码
这是电路图简图。因为我们主要处理的是软件问题,因此要相对简单。
Fritzing中硬件分布示意图
我个人的设置
现在,编辑之前获得的init.lua和main.lua文件夹, 把它们修改成下面这个样子:
-- init.lua --
-- Network Variables
ssid = "your_ssid"
pass = "your_pass"
-- Byline
print('\nAllAboutCircuits.com NodeMCU Example\n')
-- Configure Wireless Internet
wifi.setmode(wifi.STATION)
print('set mode=STATION (mode='..wifi.getmode()..')\n')
print('MAC Address: ',wifi.sta.getmac())
print('Chip ID: ',node.chipid())
print('Heap Size: ',node.heap(),'\n')
-- Configure WiFi
wifi.sta.config(ssid,pass)
dofile("main.lua")
-- main.lua --
----------------------------------
-- WiFi Connection Verification --
----------------------------------
tmr.alarm(0, 1000, 1, function()
if wifi.sta.getip() == nil then
print("Connecting to AP...\n")
else
ip, nm, gw=wifi.sta.getip()
print("IP Info: \nIP Address: ",ip)
print("Netmask: ",nm)
print("Gateway Addr: ",gw,'\n')
tmr.stop(0)
end
end)
----------------------
-- Global Variables --
----------------------
led_pin = 1
sw_pin = 2
adc_id = 0 -- Not really necessary since there's only 1 ADC...
adc_value = 512
-- Amy from Gargantia on the Verdurous Planet
blink_open = "http://i.imgur.com/kzt3tO8.png"
blink_close = "http://i.imgur.com/KS1dPa7.png"
site_image = blink_open
----------------
-- GPIO Setup --
----------------
print("Setting Up GPIO...")
print("LED")
-- Inable PWM output
pwm.setup(led_pin, 2, 512) -- 2Hz, 50% duty default
pwm.start(led_pin)
print("Switch")
-- Enable input
gpio.mode(sw_pin, gpio.INPUT)
----------------
-- Web Server --
----------------
print("Starting Web Server...")
-- Create a server object with 30 second timeout
srv = net.createServer(net.TCP, 30)
-- server listen on 80,
-- if data received, print data to console,
-- then serve up a sweet little website
srv:listen(80,function(conn)
conn:on("receive", function(conn, payload)
--print(payload) -- Print data from browser to serial terminal
function esp_update()
mcu_do=string.sub(payload,postparse[2]+1,#payload)
if mcu_do == "Update+LED" then
if gpio.read(sw_pin) == 1 then
site_image = blink_open
-- Adjust freq
pwm.setclock(led_pin, adc_value)
print("Set PWM Clock")
elseif gpio.read(sw_pin) == 0 then
site_image = blink_close
-- Adjust duty cycle
pwm.setduty(led_pin, adc_value)
print("Set PWM Duty")
end
end
if mcu_do == "Read+ADC" then
adc_value = adc.read(adc_id)
-- Sanitize ADC reading for PWM
if adc_value > 1023 then
adc_value = 1023
elseif adc_value < 0 then
adc_value = 0
end
print("ADC: ", adc_value)
end
end
--parse position POST value from header
postparse={string.find(payload,"mcu_do=")}
--If POST value exist, set LED power
if postparse[2]~=nil then esp_update()end
-- CREATE WEBSITE --
-- HTML Header Stuff
conn:send('HTTP/1.1 200 OK\n\n')
conn:send('\n')
conn:send('\n')
conn:send('\n')
conn:send('ESP8266 Blinker Thing \n')
conn:send('ESP8266 Blinker Thing!
\n')
-- Images... just because
conn:send(''" WIDTH="392" HEIGHT="196" BORDER="1">
\n')
-- Labels
conn:send('ADC Value: '
..adc_value..'
')
conn:send('PWM Frequency (Input High): '
..adc_value..'Hz')
conn:send('or
')
conn:send('PWM Duty Cycle (Input Low): '
..(adc_value * 100 / 1024)..'%
')
-- Buttons
conn:send(')
conn:send('\n')
conn:send('\n')
conn:send('\n')
conn:on("sent", function(conn) conn:close() end)
end)
end)
再一次关闭处于活动中的Screen会话,以便让Luatool工具将两个文件夹都上传到NodeMCU:
python luatool.py --port /dev/ttyUSB0 --src init.lua --dest init.lua --verbose
python luatool.py --port /dev/ttyUSB0 --src main.lua --dest main.lua --verbose
8) 使用Screen重新连接到NodeMCU Devkit,按下USER按钮以重启设备:
screen /dev/ttyUSB0 9600
当用户按下浏览器中的“Read ADC”时,浏览器将更新电位器的最新ADC读数;如果你打开了串行终端,该数值将会发送到串行终端。如果未按下按钮,输入引脚处于拉高状态,这就意味着最新的ADC读数将被用于设置LED的PWM频率。如果按下了按钮,并且输入引脚也被拉低,LED的工作周期将会得到调整。根据设置的不同的LED参数,你将在浏览器中获得不同的图像。
现在,让我们深入挖掘代码,看一看这一切都是如何实现的。Init.lua包括了来自“Hello Word, Hello WiFi”部分的绝大多数代码。它显示了关于芯片的信息,负责连接到无线网。Main.lua是所有有趣的事情发生的地方,它是对这些代码的改进。该脚本流程的目的是打印IP信息,初始化全局变量,配置I/O,以及创建一个监听80端口的TCP服务器。一旦按下按钮,浏览器将会调用一个HTTP POST方法。脚本中的string.find()方法搜索HTTP标头,并尝试找到任何提及一个名为“mcu_do”的按钮。如果发现了“mcu_do”按钮,将会调用esp_update()函数,根据分配到mcu_do的具体数值,它将会读取ADC读数或者更新LED的参数。就是这样,你将获得一些能够以有意义的方式与浏览器互动的硬件,反之亦然。
我们可以利用ESP8266和NodeMCU做很多事情,而本文提及的项目仅仅只是这些事情中的一个小部分。它可以充当MQTT代理,和运行TCP一样运行UDP,执行加密,通过I2C和SPI与外围设备交互,以及其它许多功能。NodeMCU Devkit是功能强大的硬件设备,能够实现功能非常强大的IoT应用,但是,这并不意味着它是唯一一个或者甚至不是所有项目的最佳解决方案。请多加留意,因为在蓬勃发展的IoT领域,新的软硬件解决方案将不断涌现出来。
原文来源:http://www.allaboutcircuits.com/
原文标题:How to Make an Interactive TCP Server with NodeMCU on the ESP8266
原文地址:http://www.allaboutcircuits.com/projects/how-to-make-an-interactive-tcp-server-nodemcu-on-the-esp8266