2019-1-21 18:33:19 [显示全部楼层]
7042浏览
查看: 7042|回复: 4

[ESP32系列教程] ESP32 MicroPython教程:使用Picoweb实现HTTP Webserver

[复制链接]
本 esp32 micropython 教程的目的是解释如何安装picoweb(https://github.com/pfalcon/picoweb), 这是一个用于 micropython 的 http micro web 框架。此框架允许我们在 esp32 上创建 http 服务器, 而无需担心较低级别的详细信息。

请注意, 在编写本文时, esp32 (英文)上没有正式支持 picoweb, 因此本帖提供了一种解决方法来安装它并使其正常工作。

测试是使用 micropython ide upycaft 进行的。您可以在上一篇文章:ESP32 MicroPython教程:uPyCraft IDE入门 中检查如何使用 upycaft。特别是, 我们将上传大量的脚本到 esp32 文件系统, 这是很简单的 upycaft。你可以查看此文章:ESP32 MicroPython教程:用uPyCraft执行脚本 检查如何做到这一点。

测试是使用 DFRobot 的 esp-wroom-32 设备集成在 Firebettle  esp32  板中进行的。本教程中显示的图片来自 esp32 上的测试。

视频:

更新:使用upip可以轻松完成库的安装。只需导入upip,并(在连接到WiFi网络之后)执行以下命令即可:
  1. import upip
复制代码
该命令将会安装Picoweb模块以及所有依赖项。因此,您可以忽略“解决软件依赖项问题”和“安装Picoweb”部分,相关内容仅供参考。

非常感谢Siva Kumar指出这一点。他写了一篇很好的有关Picoweb的文章。

连接WiFi

为了完成本教程,必须先将ESP32连接上WiFi,以便其安装所需要的库并运行HTTP Web server网络服务器。

之前的一篇文章已经说明了如何在MicroPython中连接WiFi网络,因此本文不再详述。而且在另一篇教程中:ESP32 / ESP8266 MicroPython教程:自动连接WiFi,也解释了如何使用MicroPython在ESP32上创建自动连接WiFi的脚本。

既然我们使用的是uPyCraft,那么就可以使用以下代码编写一段脚本在ESP32上运行,从而自动连接上WiFi。请注意,您需要修改前两个变量,以使其与您的WiFi网络证书相匹配。
  1.   import network
  2.   def connect():
  3.   ssid = "yourNetworkName"
  4.   password =  "yourNetworkPassword"
  5.   station = network.WLAN(network.STA_IF)
  6.   if station.isconnected() == True:
  7.   print("Already connected")
  8.   return
  9.   station.active(True)
  10.   station.connect(ssid, password)
  11.   while station.isconnected() == False:
  12.   pass
  13.   print("Connection successful")
  14.   print(station.ifconfig())
复制代码
上传该文件之后,您只需导入模块并调用其connect(连接)函数,就可以自动连接到WiFi。

请注意,导入模块的名称对应于文件中使用的名称。举例来说,我在代码中将其命名为wifiConnect。本教程使用的uPyCraft版本是v0.22,因此并不需要在文件末尾使用后缀.py,因为IDE会自动添加。但是在更新的版本中,可能会有所不同,因此就需要添加.py后缀。
  1.   import wifiConnect
  2.   wifiConnect.connect()
复制代码
如果您找不到上传的文件,请导入os模块并运行listdir方法。这样会为您显示出文件系统上的已上传文件。
  1.   import os
  2.   os.listdir()
复制代码
连接成功后,MicroPython命令行上就会输出IP地址列表,如图1所示。您最好将它们保存起来,因为我们后面还会用到。

TueJuly-202107205558..png
  图1 - 成功连接到WiFi后所输出的IP地址。
解决依赖项问题

Deprecated(过时内容):在引言结尾部分提供了一种更简单的Picoweb及其依赖项安装方法。

要在ESP32上使用Picoweb,必须先解决依赖项问题,因为MicroPython默认安装时缺少了一些模块。

因此,就需要安装uasyncio(https://github.com/micropython/micropython-lib/tree/master/uasyncio)和pkg_resources库(https://pypi.org/project/micropythonpkg_resources/0.1.1/)。幸运的是,我们可以使用MicroPython程序包安装程序upio来安装这些缺少的模块。

首先,导入upio(https://pypi.python.org/pypi/micropython-upip/)并安装上述模块。请注意,如果ESP32没有连接WiFi(如上节所述),则无法完成安装。通过调用upio模块的install(安装)方法,并将我们想要安装的模块名称作为一个字符串输入参数传递给这个安装方法,就可以完成安装过程。
  1.   import upip
  2.   upip.install('micropython-uasyncio')
  3.   upip.install('micropython-pkg_resources')
复制代码
请注意,安装过程会持续一段时间。安装完成后,在文件系统中就会出现一个叫做lib的新文件夹。您可以使用以下命令对此进行确认:
  1.   import os
  2.   os.listdir()
复制代码
正确的安装结果如图2所示,其中显示了新创建的库文件夹。


TueJuly-202107207012..png
  图2 - MicroPython模块的新库文件夹。

使用os模块的chdir方法可以查看库里的文件内容,但是我们暂时只需要导入两个模块来验证安装是否成功即可。
  1.   import uasyncio
  2.   import pkg_resources
复制代码
在执行上述2条命令时,如果没有出现错误,那就说明安装成功。

还有很重要的一点需要提醒您注意,utemplate库(https://github.com/pfalcon/utemplate)也是Picoweb的一个软件依赖项。只要我们不使用模板功能,那么该库就不会被导入,因此也就不会出现错误。

尽管可以在测试期间手动安装(使用与下文所述Picoweb安装相同的方法),但是因为ESP32会出现内存不足错误,所以我仍然无法使用模板功能。所以,本教程将不再考虑utemplate。


安装Picoweb

Deprecated(过时内容):在引言结尾部分提供了一种更简单的Picoweb及其依赖项安装方法。

要安装Picoweb,我们只需将GitHub库中的源文件(https://github.com/pfalcon/picoweb)复制到MicroPython文件夹即可。最重要的2个文件都在仓库内的picoweb文件夹内(https://github.com/pfalcon/picoweb/tree/master/picoweb)。

因此,为了保持文件夹的结构,我们先在ESP32 MicroPython文件系统上新建一个叫做picoweb的文件夹,如下所示。
  1.   import os
  2.   os.mkdir("picoweb")
  3.   os.listdir()
复制代码

执行完最后一行代码后,文件系统列表中就会出现新建的文件夹。

接下来,同时上传仓库内picoweb文件夹下的__init__.py和utils.py文件。如前文所述,当前版本的uPyCraft会在缺少文件名后缀时自动添加,因此您在保存文件时加不加.py后缀都没有关系。

使用图3高亮显示的相关菜单项(保存文件和上传文件),可以对utils.py文件进行上传。请注意在弹出对话框中保留原来的文件名。

TueJuly-202107208397..png
  图3 - 上传utils.py文件示例。

__init__.py文件的上传过程会持续一段时间,具体时长视文件大小而定。如果在文件上传结束后控制台显示存储器错误,直接忽略即可,不必过虑。

上传完成后,您可以重复os.listdir()命令以确认文件确实上传到了文件系统中。

最后,使用下述命令将我们的文件移动到picoweb文件夹。先要确认已经使用前面的命令导入了os模块。最后的命令会确认文件已不在根文件夹内,而是已经复制到了picoweb文件夹。
  1.   os.rename('__init__.py', 'picoweb/__init__.py')
  2.   os.rename('utils.py', 'picoweb/utils.py')
  3.   os.listdir()
复制代码
接下来就可以导入Picoweb了(不会出现任何错误)。
  1.   import picoweb
复制代码

Hello World代码

我们的hello world程序是GitHub仓库中示例代码的简化版本(https://github.com/pfalcon/picoweb/blob/master/example_webapp2.py)。请注意,尽管我们在此处是一条一条地对代码进行分析,但其实应该将其整体作为一个新建的脚本文件(以备稍后进行上传)。

当然,我们首先就要导入刚刚安装的Picoweb模块。
  1.   import picoweb
复制代码
然后,创建WebApp类的一个实例(https://github.com/pfalcon/picoweb/blob/master/picoweb/__init__.py#L63),稍后就会用到。只需使用__name__宏(https://stackoverflow.com/questions/419163/what-does-if-name-main-do)并将模块名称作为输入参数传递给它即可。
  1.   app = picoweb.WebApp(__name__)
复制代码
然后,基于Flask(http://flask.pocoo.org/)或Bottle(https://bottlepy.org/docs/dev/)网络框架定义HTTP请求的端点。接下来,我们将使用app(应用程序)对象的路由装饰器(route decorator)定义我们的新路由(https://github.com/pfalcon/picoweb/blob/master/picoweb/__init__.py#L176)。它的输入参数是触发相应函数的URL(函数将在稍后定义)。在此示例中,我们将使用索引路由,对应于“/”URL。

接下来,定义负责处理这个路由的函数(称其为索引)。该函数会接收两个参数输入。第一个参数是一个HTTPRequest类对象(https://github.com/pfalcon/picow ... web/__init__.py#L47),第二个参数是一个数据流写入器(StreamWriter),我们用它将响应发送回客户端。请注意,Picoweb会自动处理请求,并将这两个参数传递给函数。

  1.   @app.route("/")
  2.   def index(req, resp):
  3.   (...)
复制代码
在函数内部,我们会先调用picoweb模块的start_response方法(https://github.com/pfalcon/picoweb/blob/master/picoweb/__init__.py#L36)写入HTTP响应的初始部分(包括内容类型和状态代码)。

该函数会接收上述数据流写入器对象(该对象在索引函数中接收)作为输入参数。
默认状态代码是200,默认内容类型是文本/html,但是我们可以将这些数值作为参数进行传递。在此,我们暂且不会传递任何额外的数值,仅使用默认设置。

最后,使用数据流写入器的awrite方法发送剩余部分内容。我们将发送一条简单的hello world消息。

有一点需要注意,在调用每一个函数之前,我们都会使用yield from关键字。这与Python的高级特性有关,且已超出本文范畴。您可以到这里(https://www.sitepoint.com/quick-tip-understanding-the-yield-keyword-in-python/)了解有关yield的更多信息。您也可以到这里(http://stackabuse.com/python-async-await-tutorial/)了解有关异步Phthon和yield from关键字的更多内容。
  1.   yield from picoweb.start_response(resp)
  2.   yield from resp.awrite("Hello world from picoweb running on the ESP32")
复制代码
程序最后,调用app(应用程序)对象的运行run(https://github.com/pfalcon/picoweb/blob/master/picoweb/__init__.py#L229)方法来启动我们的服务器。该方法会接收一些重要的参数输入,比如服务器将要监听的主机和端口等。

如果我们不发送端口参数,则其默认值为8081。若不指定主机,则默认为127.0.0.1。此处我们不指定端口,使用默认值8081。

当然,我们要使用之前连接到WiFi网络时所获取的IP地址(参见上一节)。我们会把这个IP地址作为主机参数中的一个字符串进行传递。

请注意,有多个IP地址被打印了出来。尽管具体情况取决于路由器,但是我们应该使用以192开头而且结尾不是254的IP地址。因此,在本例中,我们将使用192.168.1.87,当然您的地址很可能不是这个样子。有一点非常重要,这是一个本地IP地址,因此在没有路由器端口转发的情况下,您无法从外网访问到服务器。

此外,我们还要把run(运行)方法的debug(调试)参数设置为True(是),这样就能将更多的信息打印到控制台上。

包含函数调用的完整代码如下所示。
  1.   import picoweb
  2.   app = picoweb.WebApp(__name__)
  3.   @app.route("/")
  4.   def index(req, resp):
  5.   yield from picoweb.start_response(resp)
  6.   yield from resp.awrite("Hello world from picoweb running on the ESP32")
  7.   app.run(debug=True, host = "192.168.1.87")
复制代码
测试代码

为了测试代码,只需将脚本上传到ESP32开发板上运行即可。我使用的是uPyCraft,所以一切易如反掌。
上传代码后,它就会在uPyCraft上自动执行。如果您使用其他方法上传代码,可能就需要将其作为一个模块导入并手动执行脚本。
脚本执行时,MicroPython串行控制台上会显示一条消息,表示服务器正在运行(如图4所示)。

TueJuly-202107206159..png
  图4 - 运行Picoweb hello world示例。

要测试ESP32正在对请求进行回应,您只需复制控制台上显示的http地址(其中包含了您的ESP32地址)即可。在uPyCraft上,您需要选择想要复制的文本,然后右键点击,选择复制。切记,使用ctrl+c命令并不能实现复制功能,相反还会使服务器停止运行。

接着,如图5所示,打开一个网页浏览器,将http地址粘贴进去。您将看到我们之前定义的hello world消息。只有在电脑与ESP32处于相同网络时,才能得到上述结果。

如果您想从WiFi网络外部访问ESP32服务器,就需要对路由器进行端口转发。这是一个高级功能(视具体路由器型号而定),超出了本文的范畴。

TueJuly-202107206548..png
  图5 - Picoweb程序的输出结果。


他写了很多有关ESP32、ESP8266的有用的教程和项目。

查看更多ESP32/ESP8266教程和项目,请点击 : ESP32教程汇总贴

gada888  版主

发表于 2019-1-21 20:48:47

技术强贴
回复

使用道具 举报

长得就像嵌入式  学徒

发表于 2019-11-18 23:12:46

厉害
回复

使用道具 举报

日月同辉  学徒

发表于 2021-2-7 14:14:35

请问能否提供“ulogging”的库文件?除“ulogging”还需添加其它文件?
SunFebruary-202102075277..png
回复

使用道具 举报

摩之卡  学徒

发表于 2021-3-28 20:21:20

  1. import sys
  2. CRITICAL = 50
  3. ERROR    = 40
  4. WARNING  = 30
  5. INFO     = 20
  6. DEBUG    = 10
  7. NOTSET   = 0
  8. _level_dict = {
  9.     CRITICAL: "CRIT",
  10.     ERROR: "ERROR",
  11.     WARNING: "WARN",
  12.     INFO: "INFO",
  13.     DEBUG: "DEBUG",
  14. }
  15. _stream = sys.stderr
  16. class Logger:
  17.     level = NOTSET
  18.     def __init__(self, name):
  19.         self.name = name
  20.     def _level_str(self, level):
  21.         l = _level_dict.get(level)
  22.         if l is not None:
  23.             return l
  24.         return "LVL%s" % level
  25.     def setLevel(self, level):
  26.         self.level = level
  27.     def isEnabledFor(self, level):
  28.         return level >= (self.level or _level)
  29.     def log(self, level, msg, *args):
  30.         if level >= (self.level or _level):
  31.             _stream.write("%s:%s:" % (self._level_str(level), self.name))
  32.             if not args:
  33.                 print(msg, file=_stream)
  34.             else:
  35.                 print(msg % args, file=_stream)
  36.     def debug(self, msg, *args):
  37.         self.log(DEBUG, msg, *args)
  38.     def info(self, msg, *args):
  39.         self.log(INFO, msg, *args)
  40.     def warning(self, msg, *args):
  41.         self.log(WARNING, msg, *args)
  42.     def error(self, msg, *args):
  43.         self.log(ERROR, msg, *args)
  44.     def critical(self, msg, *args):
  45.         self.log(CRITICAL, msg, *args)
  46.     def exc(self, e, msg, *args):
  47.         self.log(ERROR, msg, *args)
  48.         sys.print_exception(e, _stream)
  49.     def exception(self, msg, *args):
  50.         self.exc(sys.exc_info()[1], msg, *args)
  51. _level = INFO
  52. _loggers = {}
  53. def getLogger(name):
  54.     if name in _loggers:
  55.         return _loggers[name]
  56.     l = Logger(name)
  57.     _loggers[name] = l
  58.     return l
  59. def info(msg, *args):
  60.     getLogger(None).info(msg, *args)
  61. def debug(msg, *args):
  62.     getLogger(None).debug(msg, *args)
  63. def basicConfig(level=INFO, filename=None, stream=None, format=None):
  64.     global _level, _stream
  65.     _level = level
  66.     if stream:
  67.         _stream = stream
  68.     if filename is not None:
  69.         print("logging.basicConfig: filename arg is not supported")
  70.     if format is not None:
  71.         print("logging.basicConfig: format arg is not supported")
复制代码
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2021 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail