首发于micropython
ESPY教程2-Web智能硬件

ESPY教程2-Web智能硬件

大家好,我是OOPY开源团队的skaiu @江峰 。本篇文章是ESPY系列教程的第二篇,在上一篇<<ESPY教程1-Hello world>>中我们讲解了如何安装串口工具,如何使用REPL,以及如何点亮一颗LED灯。本篇文章,我们将继续探索ESPY-core将软硬件结合的神奇魅力。


在目前的所谓智能家居硬件中,很多都是通过APP或者Web来控制设备的开关和调节。看起来有点高大上,但做起来真的很难么?其实一点都不,通过阅读本文,并稍加练习,你也可以自己制作一个通过Web控制的智能设备了:-D。


试想下,我们如何才能够通过Web控制一个硬件呢?

1.我们要能够访问到一个Web页面,页面上有一些控制按钮,比如开启关闭等。 2.智能设备能够接受到我们发送过去的指令 3.智能设备能够根据接受到的指令做出反应(这一部分其实就是上篇文章中内容)


1. 创建一个简单的Web页面

首先第一个问题,通过写一个简单的HTML页面就可以解决。我们在电脑上新建一个led.html页面,并在其中输入以下内容。

<!DOCTYPE html>
<html>
<head> <title>ESP8266 LED ON/OFF</title> </head>
<center><h2>A simple webserver for turning LED's on and off with Micropython</h2></center>
<center><h3>Just For Test!(by:oopy.org)</h3></center>
<center>
  <form>
  LED0:
  <button name="LED" value="ON0" type="submit">LED ON</button>
  <button name="LED" value="OFF0" type="submit">LED OFF</button><br><br>
  </form>
  </html>
</center>


以上创建了一个HTML页面,包含了标题『ESP8266 LED ON/OFF』,两句简单的提示『A simple webserver for turning LED's on and off with Micropython』和『Just For Test!(by:oopy.org)』;以及一个表单,表单中有两个按键,一个为『LED ON』,另一个为『LED OFF』,使用Chrome浏览器打开后显示如下:



当点击LED ON时,浏览器会访问/led.html?LED=ON0 页面,当点击LED OFF时,浏览器会访问/led.html?LED=OFF0 页面。



上述功能都是在我们本地的电脑上实现的,ESPY-core又不是电脑,也能够做到上述功能么?答案是确定,一定,以及肯定的。因为,ESPY-core使用ESPY8285/8266芯片,该芯片具有完整的 且自成体系的 Wi-Fi 网络功能,既能够独立应用 ,也可以作为从机搭载于其他主机 MCU 运行 。



2. 将Web页面放置到ESPY-Core上


要访问一个设备,首先,我们需要建立一个连接。什么连接呢?网络连接。怎么建立呢?方式有两种:

1. 把ESPY-core设置成一个AP 2. 把ESPY-core设置成一个Station,加入到你的局域网中


第一种,比较简单,所以我们先来讲第一种。


所谓AP就是,无线接入点(英语:Wireless Access Point,缩写WAP)是电脑网络中一种连接无线网络至有线网络(以太网)的设备,又称为无线基站。就和你家的无线路由器类似,你可以将电脑或者手机连接上去。micropython对EPS82xx芯片引入了network库,使用该库可以对ESPY-core的所有网络和路由功能进行配置。详细内容可以参考network - network configuration - MicroPython 1.9.2 documentation


>>> import network # 导入network库
>>> ap_if = network.WLAN(network.AP_IF) #创建ap_if实例,设置WLAN为AP模式
>>> ap_if.active(True) #激活ap_if实例
>>> ap_if.ifconfig() #查看配置
('192.168.4.1', '255.255.255.0', '192.168.4.1', '208.67.222.222')

代码非常简单,总体就是先导入network库,然后创建实例,激活,检查下配置是否正确。


ifconfig()函数的返回结果从左到右分别是: IP地址,掩码,网关,DNS。从上面的配置可以看到,ESPY-core AP模式的IP地址是192.168.4.1


那我们怎么方位这个AP热点呢,打开电脑WIFI你能够发现一个名称类似OOPY-xxxxxx的热点。该热点的默认密码是oopy.org


第二种方法,将ESPY-core设置为一个Station,然后连接到你的无线局域网中。

这样你的电脑和手机就既可以访问ESPY-core模块,也可以正常上网了,当然ESPY-core在该方式也能够连接到互联网了。

>>> import network #导入network库
>>> sta_if = network.WLAN(network.STA_IF) #常见sta_if实例,设置WLAN为STA模式
>>> sta_if.active(True) # 激活sta_if配置
>>> sta_if.scan() #扫描环境中的热点
scandone
[(b'E107', b'\xbcF\x99\xc4\x98\\', 1, -82, 4, 0), (b'DJY', b'\x00\x8762\xce\xb9', 1, -74, 4, 0), (b'888', b'\x00\x876D\xb3\xba', 1, -88, 4, 0), (b'zsqzyz', b'\xb0\xd5\x9d\xa7\xc25', 1, -83, 3, 0), (b'biubiubiu', b'd\xd9T\x15H\x02', 1, -81, 3, 0), (b'ahu.dot1x', b'@\xe3\xd6t6 ', 1, -85, 5, 0), (b'ahu.portal', b'@\xe3\xd6t6!', 1, -87, 0, 0), (b'GOGOGO', b'\x00\x18\xe7\xbc\x88\xb0', 1, -91, 3, 0), (b'WiFi-shi', b'\xb0\xd5\x9d\x7f\x07d', 1, -76, 3, 0), (b'ahu.dot1x', b'@\xe3\xd6t6\xe0', 11, -83, 5, 0), (b'ahu.portal', b'@\xe3\xd6t6\xe1', 11, -83, 0, 0), (b'D-Link_DIR-600A', b'\xbc\xf6\x85\xa7\xaa\xec', 11, -80, 2, 0), (b'NETGEAR06', b'\xa0c\x91\x9b\xb0\xd0', 11, -66, 3, 0), (b'TP-LINK_5A64', b'<F\xd8\x80Zd', 3, -21, 4, 0), (b'Guest_5A64', b'6F\xd8\x80Zd', 3, -20, 4, 0), (b'F311', b'\x14u\x90\xe8\x14`', 11, -90, 4, 0), (b'CMCC_CMCC', b"\x00'\x1d1w}", 6, -40, 4, 0), (b'Dlink_Z', b'\x00!\x91x\x02P', 6, -93, 2, 0), (b'E109Teacher', b'P\xbd_\\\x01\xbc', 11, -87, 4, 0)]


上面的代码将ESPY-core设置为Station模式,然后创建了一个名为sta_if的实例,并激活了该实例。该实例使用scan()函数,扫描环境中的路由器,并将结果以一个list的形式返回。

(b'E107', b'\xbcF\x99\xc4\x98\\', 1, -82, 4, 0)
对应于(ssid, bssid, channel, RSSI, authmode, hidden)


使用下面的代码进行连接

>>> sta_if.connect('xxx', 'xxxxxxxxxx')
scandone
connected with TP-LINK_xxx, channel 3
dhcp client start...
ip:192.168.1.101,mask:255.255.255.0,gw:192.168.1.1
>>> sta_if.ifconfig()
('192.168.1.101', '255.255.255.0', '192.168.1.1', 'xxx.xx.xxx.xxx')

使用connect(ssid,password)函数进行连接,ssid就是你的路由器名称,pasword就是你路由器的密码,如果连接成功就能够看到ESPY-core模块在无线网中的地址了,在本例子中为192.168.1.101。


上面两种方式都让电脑和ESPY-core模块进行无线连接。那我们试下在ESPY-core模块上创建一个Web页面吧。

import socket #导入socket库
import machine #导入machine库

#要返回给浏览器的html内容
html = """<!DOCTYPE html>
<html>
<head> <title>ESP8266 LED ON/OFF</title> </head>
<center><h2>A simple webserver for turningLED's on and off with Micropython</h2></center>
<center><h3>Just For Test!(by:oopy.org)</h3></center>
<center>
<form>
LED0:
<button name="LED" value="ON0" type="submit">LED ON</button>
<button name="LED" value="OFF0" type="submit">LED OFF</button><br><br>
</form>
</html>
</center>
"""

#Setup Socket WebServer
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建一个socket
s.bind(('', 80)) #将socket绑定到80端口
s.listen(5) #监听80端口,最多允许5个客户端
while True:
    conn, addr = s.accept() #阻塞模式,接受一个浏览器连接
    print("Got a connection from %s" % str(addr))  #打印浏览器地址
    request = conn.recv(1024) #接受浏览器发送数据,最多接收1024bytes
    print("Content = %s" % str(request)) #打印浏览器发送数据内容
    response = html
    conn.send(response) #返回上面创建的html页面
    conn.close() #关闭连接


在看代码前,先来简述下现代浏览器原理,当我们访问一个网页时,通常是浏览器与Web服务器之间建立了一个TCP连接,然后浏览器将访问请求发送到服务器,服务器收到请求后,将请求数据返回给浏览器。浏览器渲染显示。

在上述代码中,我们先导入了一个socket库,该socket库和标准Python中的用法完全一致。创建Socket时,AF_INET指定使用IPv4协议,SOCK_STREAM指定使用面向流的TCP协议,这样,一个Socket对象就创建成功,但是还没有建立连接。然后,我们要绑定监听的地址和端口。默认绑定为本地,所以直接留空就行,端口号需要制定,Web服务的默认端口为80,我们指定为80即可。紧接着,调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量。

接下来,服务器程序通过一个永久循环来接受来自客户端的连接,accept()会等待并返回一个客户端的连接。接受数据时,调用recv(max)方法,一次最多接受指定的字节数,我们这里指定为1024,对浏览器的访问头来说足够了。接下来我们调用send()方法,将构造好的html页面返回给浏览器。当发送完毕后,调用close()方法关闭该链接,并继续等待下一个浏览器访问的来临。


使用REPL的粘贴模式输入代码


上面的代码太多了,什么时候才能敲进REPL中,不要着急。REPL中有粘贴模式。输入help()就能看到:

Welcome to MicroPython!

For online docs please visit Overview - MicroPython 1.9.2 documentation .
For diagnostic information to include in bug reports execute 'import port_diag'.

Basic WiFi configuration:

import network
sta_if = network.WLAN(network.STA_IF); sta_if.active(True)
sta_if.scan()                             # Scan for available access points
sta_if.connect("<AP_name>", "<password>") # Connect to an AP
sta_if.isconnected()                      # Check for successful connection
# Change name/password of ESP8266's AP:
ap_if = network.WLAN(network.AP_IF)
ap_if.config(essid="<AP_NAME>", authmode=network.AUTH_WPA_WPA2_PSK, password="<password>")

Control commands:
  CTRL-A        -- on a blank line, enter raw REPL mode
  CTRL-B        -- on a blank line, enter normal REPL mode
  CTRL-C        -- interrupt a running program
  CTRL-D        -- on a blank line, do a soft reset of the board
  CTRL-E        -- on a blank line, enter paste mode

For further help on a specific object, type help(obj)

按下CTRL+e键,就进入到粘贴模式。在粘贴模式下,按下Ctrl+c键取消粘贴,按下Ctrl+d键,结束粘贴。

在这里我使用的是STA模式,ESPY-core连接到我的无线路由器,且ESPY-core的地址为192.168.1.101。打开浏览器,输入ESPY-core的IP地址,得到页面如下



到这里,我们创建了一个运行在ESPY-core上的页面。下一步,让ESPY-core接受指令,我们发送过去的指令。


3. ESPY-core解析浏览器发送的指令


此时我们点击LED ON按键,REPL会有以下输出:


出现了两个请求,一个请求是/?LED=ON0,这个是正常。另一个是/favicon.ico,这个是浏览器对网站图标的请求,我们不必理会。

我们尝试将LED参数分离出来:

>>> content = b'GET /?LED=ON0 HTTP/1.1\r\nHost: 192.168.1.101\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nReferer: http://192.168.1.101/\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4\r\n\r\n'
>>> content = content.split(b'\r\n')
>>> content[0]
b'GET /?LED=ON0 HTTP/1.1'
>>> content[0].find(b'/?LED=ON0')
4

先使用split()函数,将bytearray字符串分解成一个list。list中第一个元素内容,即为请求

b'GET /?LED=ON0 HTTP/1.1'

然后再使用find()函数查找,是否包含b'/?LED=ON0',如果包含,就返回位置,如果不包含就返回-1,据此就可以判断是否点击了LED ON按键。同理,LED OFF按键也可以用这个方法判断。

联合上一篇文章中开关LED灯的代码,我们这里就可以完成Web控制LED灯的功能了。

完整代码如下:

from oopy import led
import socket
import machine


#HTML to send to browsers
html = """<!DOCTYPE html>
<html>
<head> <title>ESP8266 LED ON/OFF</title> </head>
<center><h2>A simple webserver for turningLED's on and off with Micropython</h2></center>
<center><h3>Just For Test!(by:oopy.org)</h3></center>
<center>
<form>
LED0:
<button name="LED" value="ON0" type="submit">LED ON</button>
<button name="LED" value="OFF0" type="submit">LED OFF</button><br><br>
</form>
</center>
</html>
"""

#Setup PINS
led0 = Pin(0, Pin.OUT)
#Setup Socket WebServer
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)
while True:
    conn, addr = s.accept()
    print("Got a connection from %s" % str(addr))
    request = conn.recv(1024)
    print("Content = %s" % str(request))
    request = request.split(b'\r\n')
    print("request[0] = %s" % request[0])
    if request[0].find(b'/?LED=ON0') > 0:
        led0.value(0)
    elif request[0].find(b'/?LED=OFF0') > 0:
        led0.value(1)
    response = html
    conn.send(response)
    conn.close()


录制了一个演示视频哦:


https://www.zhihu.com/video/885574668906532864


4. 总结

我们已经完成了一个通过Web控制的智能LED灯了?(其实一点都不智能!!!摔~)。但是,这个简单的实例是物联网的基石,不管你是Web或者App,甚至语音控制,都是需要将设备与设备之间建立连接。发散下,我们可以将多个ESPY-core模块相互连接,动态组网构造机器人集群,我们同样也可以让ESPY-core一直访问你关注人的知乎,当ta有新动态时,就能够随时提醒你,如此,智能设备确实能给我们的生活带来无限可能,只是我们刚开始了一小步。

这篇文章还有一些不完善的地方,比如现在的代码只是通过REPL运行,没有固定在芯片的内部FLASH中,断电就会丢失,只能从新输入。下一篇文章会讲解webREPL,让你可以自由上传代码到ESPY-core模块中,并完成一个云端自动升级的小case。

我是OOPY开源团队的skaiu @江峰,希望大家多多关注专栏,在近期我们会拿出50块ESPY-core板子,提供给大家免费试用,具体试用方案还在细化中。需要的同学,请关注近期文章。

编辑于 2017-08-28

文章被以下专栏收录