Skip to content

Latest commit

 

History

History
1271 lines (963 loc) · 44.4 KB

File metadata and controls

1271 lines (963 loc) · 44.4 KB

十四、构建物联网(IoT)气象机器

在本章中,我们将创建一个连接到互联网的天气机器,只需按下一个按钮,它就会告诉我们随机城市的天气。为了生产这种工作装置,我们将结合本书中介绍的许多概念和技术。

我们将使用第 12 章联网中所示的一些联网技术,以及第 13 章中所示的与 Adafruit Feather OLED交互的显示逻辑,涵盖如何与 Feather OLED 交互。这些不同的技术将结合在一起,通过使用 RESTful API 获取实时天气数据并将其显示在有机发光二极管OLED显示屏上,从而创建一个响应触摸按钮事件的设备。

本章是一个有用的信息来源,可以帮助您使用 MicroPython 创建互联网连接设备,这些设备易于交互,并提供丰富的视觉输出。

在本章中,我们将介绍以下主题:

  • 从互联网检索天气数据

  • 创建获取城市天气的函数

  • 随机选择城市

  • 为文本处理创建屏幕对象

  • 创建显示城市天气的函数

  • 获取天气数据时提供视觉反馈

  • 创建函数以显示随机城市的天气

  • 创建物联网按钮以显示世界各地的天气

技术要求

本章的代码文件可在以下 GitHub 存储库的Chapter14文件夹中找到:https://github.com/PacktPublishing/MicroPython-Cookbook

本章使用 Adafruit Feather HUZZAH ESP8266 和组装的 Adafruit FeatherWing OLED 128x32 OLED 附加组件制作 Feather。本章中的所有配方都使用了 Python 3.1.2。您需要应用第 10 章连接到现有 Wi-Fi 网络配方中描述的配置,控制 ESP8266。本章还将使用第 12 章联网创建等待互联网连接的功能配方中描述的wait_for_networking功能。您还需要执行第 13 章中描述的与 Adafruit Feather OLED交互的步骤。

本章中的食谱使用 Openweather 提供的天气 API 服务。此服务免费使用,但您必须注册并获得API 密钥APPID才能使用此服务。运行本章中的代码需要 API 密钥。您可以访问https://openweathermap.org/appid 获取 API 密钥。

从互联网检索天气数据

本食谱将向您展示如何使用 ESP8266 连接到 internet,并使用 RESTfulWeb 服务获取实时天气数据。我们将使用的这项服务拥有全球超过 100000 个城市的最新天气信息。每个地点都提供了大量的天气信息,因此本食谱将展示我们如何深入到我们最感兴趣的项目。

当您需要将不同的参数传递给 RESTful 调用时,或者当返回的结果非常大并且需要找到在这些大型数据集中导航的方法时,此方法在您的项目中非常有用。

准备

您需要访问 ESP8266 上的 REPL 才能运行此配方中提供的代码。

怎么做。。。

让我们按照此配方中要求的步骤进行操作:

  1. 在 REPL 中运行以下代码行:
>>> import urequests >>> >>> API_URL = 'http://api.openweathermap.org/data/2.5/weather' >>> 
  1. API_URL变量现在已经定义,我们将使用它访问天气 API。在下一个代码块中,我们定义了APPIDcity来获取天气数据。确保将APPID值替换为您的实际APPID值。现在,我们将通过组合这些变量来构建 URL,然后我们可以访问这些变量:
>>> APPID = 'put-your-API-key(APPID)-here'
>>> city = 'Berlin'
>>> url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
  1. 以下代码块将连接到天气 API 并检索天气数据:
>>> response = urequests.get(url)
>>> response
<Response object at 3fff1b00>
  1. 我们知道响应使用 JSON 格式,因此我们可以解析它并检查数据中有多少顶级键:
>>> data = response.json()
>>> len(data)
13
  1. 下一段代码检查解析的天气数据。嵌套数据很多,因此很难以其当前形式进行消化:
>>> data
{'cod': 200, 'rain': {'1h': 0.34}, 'dt': 1555227314, 'base': 'stations', 'weather': [{'id': 500, 'icon': '10d', 'main': 'Rain', 'description': 'light rain'}, {'id': 310, 'icon': '09d', 'main': 'Drizzle', 'description': 'light intensity drizzle rain'}], 'sys': {'message': 0.0052, 'country': 'DE', 'sunrise': 1555215098, 'sunset': 1555264894, 'id': 1275, 'type': 1}, 'name': 'Berlin', 'clouds': {'all': 75}, 'coord': {'lon': 13.39, 'lat': 52.52}, 'visibility': 7000, 'wind': {'speed': 3.6, 'deg': 40}, 'id': 2950159, 'main': {'pressure': 1025, 'humidity': 93, 'temp_min': 2.22, 'temp_max': 3.89, 'temp': 3.05}}
  1. MicroPython 没有pprint模块。我们将复制并粘贴数据的输出,并在计算机上的 Python REPL 上运行以下操作:
>>> data = {'cod': 200, 'rain': {'1h': 0.34}, 'dt': 1555227314, 'base': 'stations', 'weather': [{'id': 500, 'icon': '10d', 'main': 'Rain', 'description': 'light rain'}, {'id': 310, 'icon': '09d', 'main': 'Drizzle', 'description': 'light intensity drizzle rain'}], 'sys': {'message': 0.0052, 'country': 'DE', 'sunrise': 1555215098, 'sunset': 1555264894, 'id': 1275, 'type': 1}, 'name': 'Berlin', 'clouds': {'all': 75}, 'coord': {'lon': 13.39, 'lat': 52.52}, 'visibility': 7000, 'wind': {'speed': 3.6, 'deg': 40}, 'id': 2950159, 'main': {'pressure': 1025, 'humidity': 93, 'temp_min': 2.22, 'temp_max': 3.89, 'temp': 3.05}}
  1. 在计算机的 REPL 上运行下一段代码,我们将获得数据的更结构化表示:
>>> import pprint
>>> pprint.pprint(data)
{'base': 'stations',
 'clouds': {'all': 75},
 'cod': 200,
 'coord': {'lat': 52.52, 'lon': 13.39},
 'dt': 1555227314,
 'id': 2950159,
 'main': {'humidity': 93,
          'pressure': 1025,
          'temp': 3.05,
          'temp_max': 3.89,
          'temp_min': 2.22},
 'name': 'Berlin',
 'rain': {'1h': 0.34},
 'sys': {'country': 'DE',
         'id': 1275,
         'message': 0.0052,
         'sunrise': 1555215098,
         'sunset': 1555264894,
         'type': 1},
 'visibility': 7000,
 'weather': [{'description': 'light rain',
              'icon': '10d',
              'id': 500,
              'main': 'Rain'},
             {'description': 'light intensity drizzle rain',
              'icon': '09d',
              'id': 310,
              'main': 'Drizzle'}],
 'wind': {'deg': 40, 'speed': 3.6}}
>>> 
  1. 现在我们可以返回到 MicroPython REPL 并运行以下代码行来检查main键:
>>> data['main']
{'pressure': 1025, 'humidity': 93, 'temp_min': 2.22, 'temp_max': 3.89, 'temp': 3.05}
  1. 下一行代码将为我们提供柏林的温度和湿度值:
>>> data['main']['temp']
3.05
>>> data['main']['humidity']
93
>>> 
  1. 您可以使用以下代码行访问数据的“风”部分:
>>> data['wind']
{'speed': 3.6, 'deg': 40}
>>> data['wind']['speed']
3.6

通过这种方式,我们可以进一步深入了解并获得请求城市的风速值。

它是如何工作的。。。

在导入urequests库之后,我们定义了许多变量,以便我们可以继续并准备 URL 来执行 API 调用。API_URL是一个固定常数,在对 web 服务的调用之间不会改变。然后,我们定义一个变量来存储 API 键和城市值。这些值被组合成最终的 URL,然后我们使用urequests库的get函数调用它。

解析return响应并显示输出。由于数据结构如此庞大,我们使用一种技巧将这些数据移动到计算机上的 REPL,在那里我们可以使用pprint函数,并获得更清晰的返回数据输出格式。这使得识别数据结构的不同部分和开始访问嵌套数据结构中的不同数据元素变得更加容易。然后,我们使用字典中的键访问柏林市的湿度、温度和风速。

还有更多。。。

API 密钥的使用在 web 服务领域非常广泛。这个配方是一个很好的例子,说明了我们如何将这些键包含在 API 调用中,以便成功地处理它们。我们还展示了在计算机上将数据结构从 MicroPython REPL 复制到 Python REPL 的技巧。这让我们能够在这两个世界之间跳跃,访问一些模块,比如pprint,这些模块在计算机上可用,但在 MicroPython 上不可用。

另见

以下是一些参考资料,以获取更多信息:

创建获取城市天气的函数

在这个配方中,我们将创建一个函数来连接到天气 API 并获取特定城市的天气数据。我们不想直接在源代码中硬编码 API 键之类的值。因此,此配方还将向您展示如何创建 JSON 格式的配置文件,该文件可以存储不同的设置,例如 API 密钥。然后,应用程序将在启动时从该配置文件中读取值,并将其用于调用 weather web 服务。

无论是出于安全原因,还是为了在不更改应用程序源代码的情况下更轻松地调整这些设置,只要您想将配置值与代码库分开,这个方法都会特别有用。这还可以帮助您在自己的项目中将 API 调用组织为可重用函数。

准备

您需要访问 ESP8266 上的 REPL 才能运行此配方中提供的代码。

怎么做。。。

让我们按照此配方中要求的步骤进行操作:

  1. 执行 REPL 中的下一个代码块:
>>> from netcheck import wait_for_networking
>>> import urequests
>>> import json
>>> 
>>> CONF_PATH = 'conf.json'
>>> API_URL = 'http://api.openweathermap.org/data/2.5/weather'
  1. CONF_PATH变量定义 JSON 配置文件的位置。
  2. 以下内容应放入董事会根文件夹的conf.json文件中。将APPID的值替换为您的实际APPID值:
{"APPID": "put-your-API-key(APPID)-here"}
  1. 下一段代码定义了一个函数,该函数将读取和解析配置文件中提供的设置。这些设置的值随后返回给调用函数:
>>> def get_conf():
...     content = open(CONF_PATH).read()
...     return json.loads(content)
...     
...     
... 
>>> 
  1. 现在我们将调用get_conf函数,并将其结果存储到名为conf的变量中。APPID的值被检索并保存到一个变量中,以备将来使用:
>>> conf = get_conf()
>>> APPID = conf['APPID']
  1. 以下代码块定义了一个函数,该函数接收城市名称并执行该城市的天气 API 调用,并返回解析的天气数据:
>>> def get_weather(APPID, city):
...     url = API_URL + '?units=metric&APPID=' + APPID + '&q=' 
...     + city
...     return urequests.get(url).json()
...     
...     
... 
>>> 
  1. 下一段代码调用伦敦金融城的get_weather函数,并将结果存储在名为data的变量中。然后访问并打印出多个不同的数据字段:
>>> data = get_weather(APPID, 'London')
>>> 
>>> print('temp:', data['main']['temp'])
temp: 7.87
>>> print('wind:', data['wind']['speed'])
wind: 3.1
>>> print('name:', data['name'])
name: London
>>> print('country:', data['sys']['country'])
country: GB
>>> 
  1. 下一段代码应该放在main.py文件中。
from netcheck import wait_for_networking
import urequests
import json

CONF_PATH = 'conf.json'
API_URL = 'http://api.openweathermap.org/data/2.5/weather'

def get_conf():
    content = open(CONF_PATH).read()
    return json.loads(content)

def get_weather(APPID, city):
    url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
    return urequests.get(url).json()

def main():
    wait_for_networking()
    conf = get_conf()
    APPID = conf['APPID']
    data = get_weather(APPID, 'London')
    print('temp:', data['main']['temp'])
    print('wind:', data['wind']['speed'])
    print('name:', data['name'])
    print('country:', data['sys']['country'])

main()

执行此脚本时,它将连接到天气 API,并打印出伦敦金融城的许多检索到的数据元素。

它是如何工作的。。。

在进行任何 API 调用之前,主脚本首先调用wait_for_networking以确保网络已启动并正在运行。然后,它通过调用get_conf来检索应用程序配置数据,后者解析存储在配置文件中的 JSON 数据。

然后从配置设置中访问APPID的值。然后使用get_weather函数进行 API 调用。此函数接收要获取信息的城市的APPID值和名称。使用这两个值,它可以准备 URL 并进行 API 调用。

然后解析结果并返回到main函数。然后访问数据结构,从返回的 API 调用中获取大量值,并将它们与相关标签一起打印出来。

还有更多。。。

这个配方展示了一种在源代码外部存储 API 键等值的通用技术。JSON 是一种用于存储配置值的有用文件格式,尤其是在使用 MicroPython 时,因为它内置了对解析此文件格式的支持。一些应用程序还使用流行的配置文件.ini文件格式,Python 标准库支持这种格式。此 Python 模块在 MicroPython 中不作为主要库的一部分提供,因此最好在 MicroPython 项目中尽可能避免使用它。

另见

以下是一些参考资料,以获取更多信息:

随机选择城市

在此配方中,我们将使用random模块从固定的城市列表中随机选择城市。我们首先创建一个名为CITIES的全局变量来存储这些值。然后,我们可以使用random模块中的特定函数,该函数用于从值列表中选择随机项的特定目的。

然后,配方将循环 10 次,从城市列表中随机选择并输出所选城市的详细信息。当您有一个项目需要从固定的值列表中随机选择某个选项时,此配方对您特别有用。例如,您可以创建一个掷骰子的 MicroPython 项目,该项目应在每次掷骰的值 1 到 6 之间进行选择。

准备

您需要访问 ESP8266 上的 REPL 才能运行此配方中提供的代码。

怎么做。。。

让我们按照此配方中要求的步骤进行操作:

  1. 使用 REPL 运行以下代码行:
>>> import random
>>> 
>>> CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']
  1. 我们现在已经定义了一个城市列表,可以从中进行随机选择。下面的代码显示了从randomPython 模块获取随机数据的最简单方法之一:
>>> random.random()
0.0235046
>>> random.random()
0.830886
>>> random.random()
0.0738319
  1. 出于我们的目的,我们可以使用choice函数,因为它将从列表中随机选择一个项目。以下代码块使用此方法随机选择三个城市:
>>> random.choice(CITIES)
'Rome'
>>> random.choice(CITIES)
'Berlin'
>>> 
>>> random.choice(CITIES)
'Oslo'
  1. 以下代码块将循环 10 次,并在每次迭代中打印出随机选择的城市:
>>> for i in range(10):
...     city = random.choice(CITIES)
...     print('random selection', i, city)
...     
...     
... 
random selection 0 London
random selection 1 Tokyo
random selection 2 Oslo
random selection 3 Berlin
random selection 4 Bangkok
random selection 5 Tokyo
random selection 6 London
random selection 7 Oslo
random selection 8 Oslo
random selection 9 London
>>> 
  1. 下一段代码应放入main.py文件中:
import random

CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']

def main():
    for i in range(10):
        city = random.choice(CITIES)
        print('random selection', i, city)

main()

执行此脚本时,它将打印出 10 个随机选择的城市。

它是如何工作的。。。

我们首先导入将用于执行城市随机选择的random模块。反复调用random函数,以验证我们是否可以从模块中获取随机数。我们创建了一个名为CITIES的变量,它是我们希望从中进行随机选择的城市列表。然后使用random模块中的choice功能从该列表中选择一个随机选项。main函数通过调用choice函数 10 次并打印每次调用的结果来演示此逻辑。

还有更多。。。

本章只需选择随机数,即可在气象机器的运行中产生一定程度的不可预测性。因此,我们不需要担心生成的随机数的质量。然而,如果我们需要随机数来进行某些加密操作,那么我们需要更加小心这些数字是如何生成的。我们还需要详细介绍如何通过调用seed函数初始化随机数生成器。

另见

以下是一些参考资料,以获取更多信息:

为文本处理创建屏幕对象

在这个配方中,我们将创建一个Screen对象,它将更容易将多行输出写入 Feather OLED 显示器。我们正在建造的气象机器将希望利用 OLED 显示器的多线输出功能。

为了便于此输出,此配方将创建一个接收多行文本的对象,并将文本正确定位在其相关的xy坐标中。您会发现,对于经常将文本内容写入显示器并希望以自动方式处理多行输出的任何项目,此方法都很有用。

准备

您需要访问 ESP8266 上的 REPL 才能运行此配方中提供的代码。

怎么做。。。

让我们按照此配方中要求的步骤进行操作:

  1. 在 REPL 中运行以下代码行:
>>> import adafruit_ssd1306
>>> import board
>>> import busio
>>> 
>>> BLACK = 0
>>> WHITE = 1
>>> 
>>> MESSAGE = """\
... top line %s
... middle line
... last line
... """
>>> 
  1. 我们已经导入了必要的模块,并创建了一个名为MESSAGE的变量,用于生成多行输出消息。下一段代码将使用接收oled显示对象的构造函数创建Screen对象的基本结构:
>>> class Screen:
...     def __init__(self, oled):
...         self.oled = oled
...         self.oled.fill(BLACK)
...         self.oled.show()
...         
...         
... 
>>> 
  1. 在下面的代码行中,我们创建了一个与显示器交互的对象和一个Screen类的实例:
>>> i2c = busio.I2C(board.SCL, board.SDA)
>>> oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
>>> screen = Screen(oled)
  1. 现在,我们将向Screen对象添加一个方法,该方法将负责将多行文本写入显示器:
>>> class Screen:
...     def __init__(self, oled):
...         self.oled = oled
...         self.oled.fill(BLACK)
...         self.oled.show()
...         
...     def write(self, text):
...         self.oled.fill(BLACK)
...         lines = text.strip().split('\n')
...         for row, line in enumerate(lines):
...             self.oled.text(line, 0, 10 * row, WHITE)
...         self.oled.show()
...         
...         
... 
>>> 
  1. 我们现在创建一个Screen对象并调用其write方法。您现在应该可以在显示屏上看到'hello'文本:
>>> screen = Screen(oled)
>>> screen.write('hello')
  1. 下一段代码将在显示屏上打印一条多行消息,该消息占用三行:
>>> screen.write('multi \n line \n output')
>>> 
  1. 运行以下代码在显示屏上显示 10 条不同的多行消息:
>>> for i in range(10):
...     print(i)
...     screen.write(MESSAGE % i)
...     
...     
... 
0
1
2
3
4
5
6
7
8
9
>>> 
  1. 以下代码应放入screen.py文件中:
import adafruit_ssd1306
import board
import busio

BLACK = 0
WHITE = 1

class Screen:
    def __init__(self, oled):
        self.oled = oled
        self.oled.fill(BLACK)
        self.oled.show()

    def write(self, text):
        self.oled.fill(BLACK)
        lines = text.strip().split('\n')
        for row, line in enumerate(lines):
            self.oled.text(line, 0, 10 * row, WHITE)
        self.oled.show()

def get_oled():
    i2c = busio.I2C(board.SCL, board.SDA)
    return adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
  1. 下一段代码应放入main.py文件中:
from screen import Screen, get_oled

MESSAGE = """\
top line %s
middle line
last line
"""

def main():
    oled = get_oled()
    screen = Screen(oled)
    screen.write('hello')

    for i in range(10):
        print(i)
        screen.write(MESSAGE % i)

main()

当执行此脚本时,它将向 OLED 显示器打印 10 个多行文本块。

它是如何工作的。。。

screen对象向其构造函数接受一个参数。这个参数是oled变量,它将让我们与显示器交互。保存对此对象的引用,然后清除显示器上的所有像素。它还定义了一个名为write的方法。此方法接收字符串,该字符串可以是单行或多行文本。

然后清除显示,并将文本分解为字符串列表,每个字符串表示一行输出。这些行是循环的,并分别写入正确的行。处理完所有行后,将在显示屏上调用show方法在屏幕上呈现内容。此配方中的main函数设置screen对象,然后向显示器发送一条简单的hello消息。然后,它循环 10 次并生成一组多行消息,这些消息一个接一个地显示在屏幕上。

还有更多。。。

Screen对象的设计与 Python 中其他文件(如对象)的设计类似。例如,sysPython 模块有一个stdout对象,该对象有一个write方法,可以将文本输出写入屏幕。将复杂的交互(例如用于文本放置的xy定位)打包到一个单独的对象中,通常会使代码的其余部分更简单、更可读。

另见

以下是一些参考资料,以获取更多信息:

创建显示城市天气的函数

在此配方中,我们将创建一个函数,该函数采用城市名称,查找其天气信息,然后在 OLED 显示屏上显示部分信息。为了实现这一点,本配方中的功能将结合本章所述配方中的不同部分。

除了输出到 OLED 外,它还会将相同的信息打印到标准输出,以便于调试。当您想了解如何将 weather machine 之类的项目分解为在结构化设计中相互调用的独立部分时,此配方对您非常有用。

准备

您需要访问 ESP8266 上的 REPL 才能运行此配方中提供的代码。

怎么做。。。

让我们按照此配方中要求的步骤进行操作:

  1. 执行 REPL 中的下一个代码块:
>>> from screen import Screen, get_oled
>>> from netcheck import wait_for_networking
>>> import urequests
>>> import json
>>> 
>>> CONF_PATH = 'conf.json'
>>> API_URL = 'http://api.openweathermap.org/data/2.5/weather'
>>> CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']
>>> WEATHER = """\
... City: {city}
... Temp: {temp}
... Wind: {wind}
... """
>>> 
  1. 导入所需的模块后,我们创建一个名为WEATHER的新变量来存储模板,我们将使用该模板将天气信息输出到显示器。运行下一段代码来设置 screen 对象并获取 API 调用的APPID值:
>>> def get_conf():
...     content = open(CONF_PATH).read()
...     return json.loads(content)
...     
...     
... 
>>> def get_weather(APPID, city):
...     url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
...     return urequests.get(url).json()
...     
...     
... 
>>> oled = get_oled()
>>> screen = Screen(oled)
>>> wait_for_networking()
address on network: 10.0.0.38
'10.0.0.38'
>>> conf = get_conf()
>>> APPID = conf['APPID']
>>> 
  1. 在下面的代码行中,我们定义了获取屏幕的show_weather函数APPID,以及城市名称,该城市的天气信息将被获取并显示在屏幕上:
>>> def show_weather(screen, APPID, city):
...     weather = get_weather(APPID, city)
...     data = {}
...     data['city'] = city
...     data['temp'] = weather['main']['temp']
...     data['wind'] = weather['wind']['speed']
...     text = WEATHER.format(**data)
...     print('-------- %s --------' % city)
...     print(text)
...     screen.write(text)
...     
...     
... 
>>> 
  1. 运行下一段代码调用东京市的show_weather函数。您在标准输出上看到的文本也应显示在 OLED 显示屏上:
>>> show_weather(screen, APPID, 'Tokyo')
-------- Tokyo --------
City: Tokyo
Temp: 13.67
Wind: 6.7

>>> 
  1. 当我们执行以下代码块时,它将遍历所有城市,并在屏幕上显示它们的天气信息:
>>> for city in CITIES:
...     show_weather(screen, APPID, city)
...     
...     
... 
-------- Berlin --------
City: Berlin
Temp: 10.03
Wind: 3.6

-------- London --------
City: London
Temp: 8.56
Wind: 8.7

-------- Paris --------
City: Paris
Temp: 9.11
Wind: 5.1

-------- Tokyo --------
City: Tokyo
Temp: 13.55
Wind: 6.7

-------- Rome --------
City: Rome
Temp: 11.69
Wind: 6.2

-------- Oslo --------
City: Oslo
Temp: 10.13
Wind: 2.1

-------- Bangkok --------
City: Bangkok
Temp: 30.66
Wind: 5.1

>>> 
  1. 下一段代码应放入main.py文件中:
from screen import Screen, get_oled
from netcheck import wait_for_networking
import urequests
import json

CONF_PATH = 'conf.json'
API_URL = 'http://api.openweathermap.org/data/2.5/weather'
CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']
WEATHER = """\
City: {city}
Temp: {temp}
Wind: {wind}
"""

def get_conf():
    content = open(CONF_PATH).read()
    return json.loads(content)

def get_weather(APPID, city):
    url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
    return urequests.get(url).json()

def show_weather(screen, APPID, city):
    weather = get_weather(APPID, city)
    data = {}
    data['city'] = city
    data['temp'] = weather['main']['temp']
    data['wind'] = weather['wind']['speed']
    text = WEATHER.format(**data)
    print('-------- %s --------' % city)
    print(text)
    screen.write(text)

def main():
    oled = get_oled()
    screen = Screen(oled)
    wait_for_networking()
    conf = get_conf()
    APPID = conf['APPID']
    for city in CITIES:
        show_weather(screen, APPID, city)

main()

当这个脚本被执行时,它将遍历所有的城市名称,并在 OLED 显示屏上显示它们的天气信息。

它是如何工作的。。。

show_weather功能完成此配方中的大部分繁重工作。调用时,首先调用get_weather函数采集天气数据。然后它获取这些信息,并用三个值填充一个名为data的字典。这些值是城市名称、温度和风速。

然后将这些值填充到WEATHER模板中,该模板用作模板,以控制此信息在屏幕上的显示方式。生成的文本随后既被输出到标准输出显示器,也被显示在 OLED 显示器上。主函数将配置许多变量,以便进行 API 调用并更新屏幕。然后,它循环浏览城市列表,并为每个城市调用show_weather

还有更多。。。

Python 在字符串模板方面提供了很多选项。这个配方中使用的是 Python 和 MicroPython 中内置的字符串格式化函数,这使它成为一个理想的选择。通常,将模板保存在它们自己的变量中是一个好主意,就像本食谱中所做的那样。这使得更改标签和可视化预期结果变得更加容易。

show_weather功能在标准输出和 OLED 显示器上输出相同的文本。处理文本输出的一个强大方面是,您可以在许多设备上复制相同的输出。您可以进一步扩展它,并在文本日志文件中记录每个屏幕更新,以帮助调试。

另见

以下是一些参考资料,以获取更多信息:

获取数据时提供视觉反馈

在此配方中,我们将增强上一配方中的代码,以便在每次开始获取特定城市的天气数据时添加视觉反馈。本配方的第一部分是测量show_weather功能的速度。这将让我们感觉到函数是否足够慢,以至于用户可以看到它。

然后,我们将使用显示屏上的invert功能提供即时视觉反馈,表明我们已开始获取天气数据。本食谱将帮助您了解微控制器硬件约束下可能面临的性能挑战,以及如何有时克服这些限制,从而向应用程序的用户提供某种反馈。

准备

您需要访问 ESP8266 上的 REPL 才能运行此配方中提供的代码。测量执行时间和反转颜色的方法基于第 13 章与 Adafruit Feather**OLED交互的显示器上的反转颜色配方中所涵盖的内容。在继续这道菜之前,先回顾一下这道菜会很有帮助。

怎么做。。。

让我们按照此配方中要求的步骤进行操作:

  1. 使用 REPL 运行以下代码行:
>>> import time
>>> 
>>> def measure_time(label, func, args=(), count=3):
...     for i in range(count):
...         start = time.monotonic()
...         func(*args)
...         total = (time.monotonic() - start) * 1000
...         print(label + ':', '%s ms' % total)
...         
...         
... 
>>> 
  1. measure_time功能现在已经定义。继续之前,请确保将前一配方中main.py文件中的所有函数定义、模块导入和全局变量粘贴到 REPL 中。然后,运行以下代码块:
>>> oled = get_oled()
>>> screen = Screen(oled)
>>> wait_for_networking()
address on network: 10.0.0.38
'10.0.0.38'
>>> conf = get_conf()
>>> APPID = conf['APPID']
>>> 
  1. 我们现在已经具备了测量show_weather函数执行时间所需的一切。运行下一段代码以进行三次测量:
>>> measure_time('show_weather', show_weather, [screen, APPID, 'Rome'])
-------- Rome --------
City: Rome
Temp: 9.34
Wind: 2.6

show_weather: 2047.0 ms
-------- Rome --------
City: Rome
Temp: 9.3
Wind: 2.6

show_weather: 1925.9 ms
-------- Rome --------
City: Rome
Temp: 9.36
Wind: 2.6

show_weather: 2019.04 ms
>>> 
  1. 通过这些测量,我们可以看到每个调用大约需要 2 秒的执行时间。我们现在将在show_weather函数的开始和结束处添加对invert方法的调用,如下面的代码块所示:
>>> def show_weather(screen, APPID, city):
...     screen.oled.invert(True)
...     weather = get_weather(APPID, city)
...     data = {}
...     data['city'] = city
...     data['temp'] = weather['main']['temp']
...     data['wind'] = weather['wind']['speed']
...     text = WEATHER.format(**data)
...     print('-------- %s --------' % city)
...     print(text)
...     screen.write(text)
...     screen.oled.invert(False)
...     
...     
... 
>>> 
  1. 以下代码块在执行时,将在show_weather函数执行的开始和结束时提供视觉反馈:
>>> show_weather(screen, APPID, 'Rome')
-------- Rome --------
City: Rome
Temp: 9.3
Wind: 2.6

>>> 
  1. 下一段代码应放入main.py文件中:
from screen import Screen, get_oled
from netcheck import wait_for_networking
import urequests
import json
import time

CONF_PATH = 'conf.json'
API_URL = 'http://api.openweathermap.org/data/2.5/weather'
CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']
WEATHER = """\
City: {city}
Temp: {temp}
Wind: {wind}
"""

def get_conf():
    content = open(CONF_PATH).read()
    return json.loads(content)

def get_weather(APPID, city):
    url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
    return urequests.get(url).json()

def show_weather(screen, APPID, city):
    screen.oled.invert(True)
    weather = get_weather(APPID, city)
    data = {}
    data['city'] = city
    data['temp'] = weather['main']['temp']
    data['wind'] = weather['wind']['speed']
    text = WEATHER.format(**data)
    print('-------- %s --------' % city)
    print(text)
    screen.write(text)
    screen.oled.invert(False)

def main():
    oled = get_oled()
    screen = Screen(oled)
    wait_for_networking()
    conf = get_conf()
    APPID = conf['APPID']
    for city in CITIES:
        show_weather(screen, APPID, city)
        time.sleep(1)

main()

当这个脚本被执行时,它将在每个城市中循环,并使用新的反转颜色视觉反馈调用show_weather函数。

它是如何工作的。。。

measure_time函数帮助我们测量show_weather函数的执行时间。此函数用于从 internet 获取数据、解析数据,然后执行许多屏幕操作以显示数据。测量的执行时间约为 2 秒。与台式计算机相比,微控制器的计算能力有限。在桌面上这样的操作需要几百毫秒,但在微控制器上可能需要更长的时间。由于这种明显的执行时间,我们通过在执行开始时反转颜色来增强show_weather函数。此颜色反转将在几毫秒内显示,并将在完成任何其他处理之前显示。然后,在执行结束时,反转的颜色将返回到其正常状态,以指示函数已完成其执行。

还有更多。。。

在后面的配方中,当我们将按钮连接到show_weather功能时,视觉反馈将变得非常重要。屏幕更新中的 2 秒延迟非常明显,用户将引导某种视觉反馈,以指示机器正在执行操作,而不是被卡住。本配方中所示的invert方法非常适合此目的,并且不需要太多额外代码即可实现其结果。

另见

以下是一些参考资料,以获取更多信息:

创建函数以显示随机城市的天气

在此配方中,我们将创建一个函数,该函数将选择一个随机城市,并在每次调用时在屏幕上显示其天气信息。该功能将使用random模块中的choice功能选择一个随机城市,然后使用show_weather功能显示该城市的天气信息。

无论何时,当您想要在项目中添加一些随机性,以便在与该设备的交互中有更大程度的不可预测性时,此配方对您都很有用。这会在您的项目中产生一些意外和令人惊讶的行为,使它们更有趣地进行交互。

准备

您需要访问 ESP8266 上的 REPL 才能运行此配方中提供的代码。

怎么做。。。

让我们检查此配方中所需的步骤:

  1. 在 REPL 中运行以下代码行:
>>> import random
>>> 
>>> def show_random_weather(screen, APPID):
...     city = random.choice(CITIES)
...     show_weather(screen, APPID, city)
...     
...     
... 
>>> 
  1. show_random_weather功能现在已经定义。继续之前,请确保将前一配方中main.py文件中的所有函数定义、模块导入和全局变量粘贴到 REPL 中。然后,运行以下代码块:
>>> oled = get_oled()
>>> screen = Screen(oled)
>>> wait_for_networking()
address on network: 10.0.0.38
'10.0.0.38'
>>> conf = get_conf()
>>> APPID = conf['APPID']
>>> 
  1. 运行下一段代码,将显示随机城市的天气:
>>> show_random_weather(screen, APPID)
-------- Bangkok --------
City: Bangkok
Temp: 30.01
Wind: 5.1

>>> 
  1. 现在我们将循环三次并调用show_random_weather函数来测试其功能:
>>> for i in range(3):
...     show_random_weather(screen, APPID)
...     
...     
... 
-------- Rome --------
City: Rome
Temp: 9.08
Wind: 2.6

-------- Berlin --------
City: Berlin
Temp: 8.1
Wind: 3.6

-------- London --------
City: London
Temp: 5.41
Wind: 6.2

>>> 
  1. 下一段代码应放入main.py文件中:
from screen import Screen, get_oled
from netcheck import wait_for_networking
import urequests
import json
import time
import random

CONF_PATH = 'conf.json'
API_URL = 'http://api.openweathermap.org/data/2.5/weather'
CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']
WEATHER = """\
City: {city}
Temp: {temp}
Wind: {wind}
"""

def get_conf():
    content = open(CONF_PATH).read()
    return json.loads(content)

def get_weather(APPID, city):
    url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
    return urequests.get(url).json()

def show_weather(screen, APPID, city):
    screen.oled.invert(True)
    weather = get_weather(APPID, city)
    data = {}
    data['city'] = city
    data['temp'] = weather['main']['temp']
    data['wind'] = weather['wind']['speed']
    text = WEATHER.format(**data)
    print('-------- %s --------' % city)
    print(text)
    screen.write(text)
    screen.oled.invert(False)

def show_random_weather(screen, APPID):
    city = random.choice(CITIES)
    show_weather(screen, APPID, city)

def main():
    oled = get_oled()
    screen = Screen(oled)
    wait_for_networking()
    conf = get_conf()
    APPID = conf['APPID']
    for i in range(3):
        show_random_weather(screen, APPID)

main()

当这个脚本被执行时,它将循环三次,并在每次迭代中选择一个随机城市,该城市将显示其天气信息。

它是如何工作的。。。

show_random_weather函数需要两个参数作为其输入。screen 和APPID需要作为输入参数,以进行所需的 API 调用并更新屏幕内容。在CITIES列表中调用random模块的choice功能,选择一个随机城市。一旦选择了这个城市,就可以使用show_weather功能获取和显示它的天气。此配方中的main函数循环三次,并在每次for循环迭代中调用show_random_weather函数。

还有更多。。。

这个食谱是连接互联网的气象机器最后剩下的部分之一。我们已经构建并测试了该应用程序的每一部分,以确认在前一层上构建附加逻辑之前,每一部分都已完成。此配方的所有代码和逻辑都包含在自己的函数中,这提高了代码的可读性,也有助于故障排除。如果发生任何错误,通过确切地知道在哪个函数中引发了异常,将更容易进行故障排除。

另见

以下是一些参考资料,以获取更多信息:

创建物联网按钮以显示世界各地的天气

在本食谱中,我们将为连接互联网的气象机器添加最后一次触摸。在本章中,我们将采用食谱中介绍的大部分代码,并在main函数中添加event循环,以便我们可以通过显示世界各地随机城市的天气来对按键事件做出反应。本食谱将为您提供一个很好的示例,说明如何向现有代码库添加一个event循环,以创建用户交互。

准备

您需要访问 ESP8266 上的 REPL 才能运行此配方中提供的代码。

怎么做。。。

让我们按照此配方中要求的步骤进行操作:

  1. 执行 REPL 中的下一个代码块:
>>> from machine import Pin
>>> 
>>> BUTTON_A_PIN = 0
>>> 
  1. Pin对象现在已导入,以便我们可以与电路板的按钮进行交互。继续之前,请确保将前一配方中main.py文件中的所有函数定义、模块导入和全局变量粘贴到 REPL 中。然后运行以下代码块:
>>> button = Pin(BUTTON_A_PIN, Pin.IN, Pin.PULL_UP)
>>> 
  1. button变量现在可以读取按钮 A 的状态。运行下一个代码块来检测按钮 A 当前是否被按下:
>>> not button.value()
False
>>> 
  1. 按下按钮 A 时,执行以下代码块:
>>> not button.value()
True
>>> 
  1. 运行下一段代码,准备screenAPPID变量:
>>> oled = get_oled()
>>> screen = Screen(oled)
>>> wait_for_networking()
address on network: 10.0.0.38
'10.0.0.38'
>>> conf = get_conf()
>>> APPID = conf['APPID']
>>> 
  1. 下面的代码块将启动一个event循环。每次按下按钮 A 时,应显示随机城市的天气:
>>> while True:
...     if not button.value():
...         show_random_weather(screen, APPID)
...         
...         
... 
-------- London --------
City: London
Temp: 6.62
Wind: 4.6

-------- Paris --------
City: Paris
Temp: 4.53
Wind: 2.6

-------- Rome --------
City: Rome
Temp: 10.39
Wind: 2.6
>>> 
  1. 下一段代码应放入main.py文件中:
from screen import Screen, get_oled
from netcheck import wait_for_networking
from machine import Pin
import urequests
import json
import time
import random

BUTTON_A_PIN = 0
CONF_PATH = 'conf.json'
API_URL = 'http://api.openweathermap.org/data/2.5/weather'
CITIES = ['Berlin', 'London', 'Paris', 'Tokyo', 'Rome', 'Oslo', 'Bangkok']
WEATHER = """\
City: {city}
Temp: {temp}
Wind: {wind}
"""

def get_conf():
    content = open(CONF_PATH).read()
    return json.loads(content)

def get_weather(APPID, city):
    url = API_URL + '?units=metric&APPID=' + APPID + '&q=' + city
    return urequests.get(url).json()

def show_weather(screen, APPID, city):
    screen.oled.invert(True)
    weather = get_weather(APPID, city)
    data = {}
    data['city'] = city
    data['temp'] = weather['main']['temp']
    data['wind'] = weather['wind']['speed']
    text = WEATHER.format(**data)
    print('-------- %s --------' % city)
    print(text)
    screen.write(text)
    screen.oled.invert(False)

def show_random_weather(screen, APPID):
    city = random.choice(CITIES)
    show_weather(screen, APPID, city)

def main():
    oled = get_oled()
    screen = Screen(oled)
    wait_for_networking()
    conf = get_conf()
    APPID = conf['APPID']
    button = Pin(BUTTON_A_PIN, Pin.IN, Pin.PULL_UP)
    show_random_weather(screen, APPID)
    while True:
        if not button.value():
            show_random_weather(screen, APPID)

main()

当这个脚本被执行时,它将启动一个event循环,每次按下按钮 a 时,该循环将获取并显示一个随机城市的天气。

它是如何工作的。。。

此配方中的main函数创建一个名为ButtonPin对象,该对象将连接到按钮 a。我们可以使用此button变量轮询按钮的状态。然后,我们显示随机城市的天气,这样应用程序的启动状态就是显示屏上显示的天气。然后,启动一个infinite循环,这将是我们处理任何按钮事件的event循环。在每个循环中,我们检查按钮 A 是否被按下。如果是,则调用show_random_weather函数在屏幕上显示随机城市的天气。

还有更多。。。

此配方只需按下一个按钮即可显示随机天气。我们可以将按钮 B 和 C 连接到主event回路,让它们产生其他功能。按钮 A 可能会改变城市,而按钮 B 和按钮 C 可以让您滚动并查看与当前所选城市相关的更多天气信息。下一张照片显示了互联网连接的气象机器在显示东京市天气信息时的样子:

还可以更改此配方以从 web 服务获取和显示任何信息。您可以获取最新的新闻标题并显示它们,或者显示来自 RESTfulJokeAPI 的随机笑话。天空是多行文字显示和互联网连接的极限。

另见

以下是一些参考资料,以获取更多信息: