原文:
zh.annas-archive.org/md5/7FABA31DD38F615362E1254C67CC152E译者:飞龙
第十一章:测量距离和检测运动
欢迎来到我们的最后一个基于核心电子学的章节。在上一章中,我们学习了如何以复杂的方式控制三种不同形式的电机。在本章中,我们将把注意力集中在使用树莓派和电子设备检测运动和测量距离。
检测运动对于自动化项目非常有用,例如当您走进房间或建筑物时点亮灯光,警报系统,建筑物计数器或检测轴的旋转。我们将研究两种运动检测技术,包括使用被动红外(PIR)传感器来检测人(或动物)的存在的热检测,以及数字霍尔效应传感器,它可以检测磁场的存在(或者更宽泛地说,我们可以说霍尔效应传感器可以检测到磁铁移过它的时候)。
距离测量对于许多项目也很有用,从碰撞检测电路到测量水箱水位。我们将研究两种距离测量形式,包括使用超声波声音传感器,可以测量大约 2 厘米到 4 米的距离,以及可以测量磁场接近度的模拟霍尔效应传感器,可以测量到毫米级的磁场接近度。
以下是本章的内容:
-
使用 PIR 传感器检测运动
-
使用超声波传感器测量距离
-
使用霍尔效应传感器检测运动和距离
技术要求
要执行本章的练习,您需要以下内容:
-
树莓派 4 型 B
-
Raspbian OS Buster(带桌面和推荐软件)
-
最低 Python 版本 3.5
这些要求是本书中代码示例的基础。可以合理地期望代码示例应该可以在树莓派 3 型 B 或不同版本的 Raspbian OS 上无需修改即可工作,只要您的 Python 版本是 3.5 或更高。
您可以在 GitHub 存储库的chapter11文件夹中找到本章的源代码,网址为github.com/PacktPublishing/Practical-Python-Programming-for-IoT。
您需要在终端中执行以下命令来设置虚拟环境并安装本章代码所需的 Python 库:
$ cd chapter11 # Change into this chapter's folder
$ python3 -m venv venv # Create Python Virtual Environment
$ source venv/bin/activate # Activate Python Virtual Environment
(venv) $ pip install pip --upgrade # Upgrade pip
(venv) $ pip install -r requirements.txt # Install dependent packages
从requirements.txt安装以下依赖项:
-
PiGPIO:PiGPIO GPIO 库(
pypi.org/project/pigpio) -
ADS1X15:ADS11x5 ADC 库(
pypi.org/project/adafruit-circuitpython-ads1x15)
本章练习所需的电子元件如下:
-
1 x 1kΩ电阻
-
1 x 2kΩ电阻
-
1 x HC-SR501 PIR 传感器(数据表:
www.alldatasheet.com/datasheet-pdf/pdf/1131987/ETC2/HC-SR501.html) -
1 x A3144 霍尔效应传感器(非锁存)(数据表:
www.alldatasheet.com/datasheet-pdf/pdf/55092/ALLEGRO/A3144.html) -
1 x AH3503 霍尔效应传感器(比率式)(数据表:
www.alldatasheet.com/datasheet-pdf/pdf/1132644/AHNJ/AH3503.html) -
1 x HC-SR04 或 HC-SR04P 超声波距离传感器(数据表:
tinyurl.com/HCSR04DS) -
用于霍尔效应传感器的小磁铁
HC-SR04 有两种变体可用。更常见的 HC-SR04,输出 5 伏逻辑和 HC-SR04P,可以在 3 伏至 5.5 伏之间工作。这两种模块都适用于本章的练习。
使用 PIR 传感器检测运动
PIR 传感器是一种可以检测到物体(例如人)发出的红外光(热量)的设备。我们在周围的应用中看到这些类型的传感器,如安全系统和对我们的存在做出反应的自动门和灯。PIR 中的“被动”意味着传感器只是检测运动。要检测什么移动和如何移动,你需要一个主动红外设备,比如热成像摄像头。
PIR 传感器有几种不同的形式和品种;然而,它们的基本用法是相同的——它们作为一个简单的数字开关。当它们没有检测到运动时,它们输出数字LOW,当检测到运动时,它们输出数字HIGH。
下图显示了我们将在示例中使用的 HC-SR501 PIR 传感器模块。图片中显示了模块的顶部、底部和 PIR 传感器的常见原理图符号:
图 11.1 - HC-SR501 PIR 传感器模块
一些 PIR 传感器,包括我们的 HC-SR501,上面有设置和校准调整。这些调整用于改变传感器的灵敏度范围和触发模式。在没有板载校准的情况下使用 PIR 设备意味着我们需要在代码中自行处理灵敏度调整。
关于 HC-SR501,它的端子如下:
-
GND:接地。
-
Vcc:连接到 5 伏至 20 伏的电源。
-
数据:我们连接到 GPIO 引脚的数字输出。当 PIR 检测到运动时,此引脚变为
HIGH;否则,在没有运动的情况下保持LOW。HC-SR501 输出 3.3 伏信号,尽管它需要 5 至 20 伏的电源。接下来我们将看到,板载的灵敏度调整、定时调整、触发模式跳线会影响数据引脚在检测到运动时保持HIGH的方式、时间和持续时间。
HC-SR501 的板载设置如下:
-
灵敏度调整:改变有效的移动感应范围,从大约 3 米到大约 7 米。使用小螺丝刀旋转此设置的拨号。
-
时间延迟调整:在检测到运动后数据端口保持
HIGH的时间。调整范围约为 5 秒至 300 秒。使用小螺丝刀旋转此设置的拨号。 -
触发模式跳线:在持续检测到运动的情况下,此跳线设置意味着在时间延迟到期后(由时间延迟调整设置),数据端口将执行以下操作:
-
保持
HIGH。这是可重复触发设置,通过将跳线放置在H位置来设置。 -
恢复为
LOW。这是单次触发设置,通过将跳线放置在L位置来设置。
你的 PIR 的最佳设置将取决于你打算如何使用它以及你部署传感器的环境。我的建议是,在完成电路搭建并运行后续部分的示例代码后,尝试调整设置,以了解如何改变设置会影响传感器的操作。记得查阅 HC-SR501 的数据表,以获取有关传感器及其板载设置的更多信息。
让我们把我们的 PIR 传感器接线并连接到我们的树莓派。
创建 PIR 传感器电路
在这一部分,我们将把我们的 PIR 传感器连接到我们的树莓派。以下是我们即将构建的电路的原理图。正如你所看到的,从 PIR 传感器的角度来看,它的布线相对简单:
图 11.2 - PIR 传感器模块电路
让我们按照下图所示将其连接到我们的树莓派:
图 11.3 - PIR 传感器电路面包板布局
以下是创建面包板搭建的步骤。步骤编号与图 11.3中的黑色圆圈中的编号相匹配:
-
将您的 PIR 传感器的每个端子连接到面包板上。您将需要三根公对公跳线。
-
将树莓派上的 5 伏特引脚连接到 PIR 的 Vcc 端使用的面包板行。PIR 传感器只使用少量电流,因此将 5 伏特 Vcc 引脚直接连接到树莓派上是可以的。
-
将树莓派上的 GND 引脚连接到 PIR 的 GND 端使用的面包板行。
-
将树莓派上的 GPIO 21 引脚连接到 PIR 的数据端使用的面包板行。
重要提示:我们参考的 HC-SR501 PIR 传感器需要>4.5 伏特的电源(Vcc),并在其 Sig 输出引脚上输出 3.3 伏特。如果您使用的是不同的 PIR 传感器,请查阅其数据表并检查输出引脚电压。如果它大于 3.3 伏特,您将需要使用电压分压器或逻辑电平转换器。在下一节中,我们将涵盖这种确切的情况,当我们将电压分压器与 HC-SR04 传感器配对,将其 5 伏特输出转换为树莓派友好的 3.3 伏特。
创建电路后,我们将继续并运行我们的 PIR 示例代码,这将让我们检测运动。
运行和探索 PIR 传感器代码
我们的 PIR 电路代码可以在chapter11/hc-sr501.py文件中找到。在继续之前,请查看源代码,以对该文件的内容有一个广泛的了解。
HC-SR501 数据表规定,传感器在上电后需要大约 1 分钟的时间来初始化和稳定自身。如果在传感器变得稳定之前尝试使用传感器,可能会在启动程序时收到一些错误的触发。
在终端中运行hc-sr501.py文件。当 HC-SR501 检测到运动时,程序将在终端上打印Triggered,或者在未检测到运动时打印Not Triggered,如下所示:
(venv) $ python hc-sr501.py
PLEASE NOTE - The HC-SR501 Needs 1 minute after power on to initialize itself.
Monitoring environment...
Press Control + C to Exit
Triggered.
Not Triggered.
... truncated ...
如果您的程序没有按预期响应,请尝试调整我们之前在使用 PIR 传感器检测运动部分中讨论的灵敏度调整、时间延迟调整或触发模式跳线设置中的一个或多个。
您可以将 HC-SR501 视为基本开关。它要么是打开的(HIGH),要么是关闭的(LOW),就像普通的按钮开关一样。实际上,我们的代码类似于第二章中使用 Python 和物联网入门部分中介绍的 PiGPIO 按钮示例。我们只会在这里简要介绍核心代码部分;但是,如果您需要更深入的解释或复习,请重新查看第二章中的 PiGPIO 部分,使用 Python 和物联网入门。
让我们讨论示例代码。首先,我们在第 1 行开始设置我们的 GPIO 引脚为带有下拉使能的输入引脚,而在第 2 行,我们启用了去抖动。我们的 HC-SR501 模块实际上不需要在代码中激活下拉,也不需要去抖动;但是,我为了完整性而添加了它:
# ... truncated ...
GPIO = 21
# Initialize GPIO
pi.set_mode(GPIO, pigpio.INPUT) # (1)
pi.set_pull_up_down(GPIO, pigpio.PUD_DOWN)
pi.set_glitch_filter(GPIO, 10000) # microseconds debounce # (2)
接下来,在第 3 行,我们定义了callback_handler()函数,每当 GPIO 引脚改变其HIGH/LOW状态时都会被调用:
def callback_handler(gpio, level, tick): # (3)
""" Called whenever a level change occurs on GPIO Pin.
Parameters defined by PiGPIO pi.callback() """
global triggered
if level == pigpio.HIGH:
triggered = True
print("Triggered")
elif level == pigpio.LOW:
triggered = False
print("Not Triggered")
最后,在第 4 行,我们注册了回调函数。正是第二个参数pigpio.EITHER_EDGE导致callback_handler()在 GPIO 变为HIGH或LOW时被调用:
# Register Callback
callback = pi.callback(GPIO, pigpio.EITHER_EDGE, callback_handler) # (4)
作为对比,在第二章中,使用 Python 和物联网入门,对于我们的按钮示例,此参数为pigpio.FALLING_EDGE,意味着只有在按下按钮时才会调用回调,而松开按钮时不会调用。
正如我们所见,PIR 传感器只能检测物体的接近 - 例如,有人靠近我们的传感器吗? - 但它无法告诉我们物体的距离有多远或多近。
我们现在已经学会了如何创建和连接一个简单的 PIR 传感器电路到我们的树莓派,并且学会了如何在 Python 中使用它来检测运动。有了这些知识,你现在可以开始构建自己的运动检测项目,比如结合第七章中的示例,打开和关闭东西,或者作为你自己的警报和监控系统的重要部分。
接下来,我们将看一下能够估算距离的传感器。
用超声波传感器测量距离
在上一节中,我们学会了如何使用 PIR 传感器检测运动。正如我们发现的那样,我们的 PIR 传感器是一个数字设备,通过使其输出为数字HIGH来表示检测到运动。
现在是时候学习如何用树莓派测量距离了。有各种各样的传感器可以执行这项任务,它们通常要么使用声音,要么使用光。我们的示例将基于流行的 HC-SR04 超声波距离传感器(它使用声音),如下图所示:
图 11.4 - HC-SR04 超声波距离传感器模块
你通常会在现代汽车保险杠上找到超声波距离传感器(它们通常是小圆圈,这是与前面图中的 HC-SR04 不同的形状)。这些传感器计算你的车和附近物体之间的距离,例如,当你越来越接近物体时,会让车内的蜂鸣器越来越快地响起。
另一个常见的应用是用于测量液体水平,比如水箱中的水位。在这种情况下,(防水)超声波传感器测量从水箱顶部到水位的距离(声音脉冲反射在水上)。然后可以将测得的距离转换为水箱的大致容量。
让我们更仔细地看一下我们的 HC-SR04 传感器。参考 HC-SR04 数据表中的核心规格如下:
-
电源电压 5 伏(HC-SR04)或 3 伏至 5.5 伏(HC-SR04P)
-
逻辑电压 5 伏(HC-SR04)或 3 伏至 5.5 伏(HC-SR04P)
-
工作电流 15 毫安,静态电流 2 毫安
-
有效测量范围为 2 厘米至 4 米,精度为+/- 0.3 厘米
-
10 微秒的触发脉冲宽度。我们将在标题为HC-SR04 距离测量过程的部分重新讨论这个脉冲宽度并进行更多讨论。
SC-SR04 有两个圆柱体。它们如下:
-
T或TX:产生超声波脉冲的发射器
-
R或RX:检测超声波脉冲的接收器
我们将在下一节讨论发射器和接收器如何一起工作来测量距离。
HC-SR04 有四个端子,它们如下:
-
Vcc:电源(树莓派 5 伏引脚将是可以的,考虑到最大电流为 15 毫安)。
-
GND:接地连接。
-
TRIG:触发输入端子 - 当
HIGH时,传感器发送超声波脉冲。 -
ECHO:回声输出端子 - 当
TRIG变为HIGH时,此引脚变为HIGH,然后在检测到超声脉冲时变为LOW。
我们将在标题为HC-SR04 距离测量过程的部分讨论TRIG和ECHO端子的使用。
现在我们了解了超声波距离传感器的基本用法和 HC-SR04 的基本特性和布局,让我们讨论一下它是如何工作的。
超声波距离传感器的工作原理
让我们看看发射器(TX)和接收器(RX)如何一起工作来测量距离。超声波传感器的基本工作原理如下图所示:
图 11.5 - 超声波距离传感器操作
以下是发生的事情:
-
首先,传感器从发射器(TX)发送超声波脉冲。
-
如果传感器前面有物体,这个脉冲会反弹到物体上并返回到传感器,并被接收器(RX)检测到。
-
通过测量发送脉冲和接收脉冲之间的时间,我们可以计算传感器和物体之间的距离。
了解了传感器工作原理的高层次理解后,接下来,我们将深入讨论如何使用 HC-SR04 上的 TRIG 和 ECHO 端子一起估算距离的过程。
HC-SR04 距离测量过程
在本节中,我们将介绍使用 HC-SR04 测量距离的过程。 如果这一点不立即明白,不要担心。 我在这里提供了详细信息作为背景材料,因为这是我们示例程序实现的逻辑过程,以使传感器工作。 您还会在传感器的数据表中找到这个过程的记录。
我们通过正确使用和监控 TRIG 和 ECHO 引脚来测量 HC-SR04 的距离。 过程如下:
-
将 TRIG 引脚拉高 10 微秒。 拉高 TRIG 也会使 ECHO 引脚变高。
-
启动计时器。
-
等待以下任一情况发生:
-
ECHO 变为
LOW -
经过 38 毫秒(从数据表中,这是>4 米的时间)
- 停止计时器。
如果经过了 38 毫秒,我们得出结论认为传感器前面没有物体(至少在有效范围内的 2 厘米到 4 米之间)。 否则,我们将经过的时间除以 2(因为我们想要传感器和物体之间的时间间隔,而不是传感器到物体再返回到传感器),然后使用基本物理学,使用以下公式计算传感器和物体之间的距离:
在这里,我们有以下内容:
-
d是以米为单位的距离。
-
v是以米/秒为单位的速度,我们使用声速,大约为 20°C(68°F)时的 343 米/秒。
-
t是以秒为单位的时间。
HC-SR04 只会估算距离。 有几个参数会影响其准确性。 首先,正如之前暗示的,声速随温度变化而变化。 其次,传感器的分辨率为±0.3 厘米。 此外,被测物体的大小,物体相对于传感器的角度,甚至物体的材质都会影响 ECHO 的定时结果,从而影响计算出的距离。
通过对如何使用 HC-SR04 估算距离的基本理解,让我们构建我们的电路,将 HC-SR04 连接到我们的树莓派。
构建 HC-SR04 电路
是时候构建我们的 HC-SR04 电路了。 我们电路的原理图如下图所示。 这种布线适用于 HC-SR04 或 HC-SR04P 模块:
图 11.6 - HC-SR04(5 伏逻辑 ECHO 引脚)电路
作为提醒,HC-SR04 模块(或像这样连接到 5 伏电源的 HC-SR04P)是一个 5 伏逻辑模块,因此您会注意到电路中由两个电阻器创建的电压分压器将 5 伏转换为 3.3 伏。 如果您需要关于电压分压器的复习,我们在第六章中详细介绍了它们,软件工程师的电子学 101。
让我们在面包板上构建这个电路:
图 11.7 - HC-SR04 电路面包板布局(第一部分)
以下是创建面包板构建的第一部分的步骤。 步骤编号与图 11.7中的黑色圆圈中的数字相匹配:
-
将 1kΩ电阻(R1)放入面包板中。
-
将 2kΩ电阻(R2)放入面包板中。 第二个电阻的一个腿与第一个电阻的一个腿共用一行。 在插图中,这可以在右侧银行的第 21 行中看到。
-
将左侧和右侧的负电源导轨连接在一起。
-
将树莓派上的 GND 引脚连接到左侧电源轨的负电源。
-
将第二条 2kΩ电阻(R2)连接到右侧电源轨的负电源。
-
将 HC-SR04 传感器上的 GND 端子连接到右侧电源轨的负电源。
-
将 HC-SR04 传感器上的 Vcc 端子连接到右侧电源轨的正电源。
确保 R1 和 R2 电阻的连接方式如前图所示 - 即 R1(1kΩ)连接到 HC-SR04 的 ECHO 引脚。由 R1 和 R2 创建的电压分压器将 ECHO 引脚的 5 伏特转换为~3.3 伏特。如果您将电阻安装反了,5 伏特将转换为~1.67 伏特,这不足以在树莓派上注册逻辑HIGH。
既然我们已经布置好了基本组件并进行了一些初步的接线连接,让我们完成我们的构建:
图 11.8 - HC-SR04 电路面包板布局(第二部分)
以下是要遵循的步骤。步骤编号与图 11.8中黑色圆圈中的数字相匹配:
-
将树莓派上的 GPIO 20 连接到 HC-SR04 传感器上的 Trig 端子。
-
将树莓派上的 GPIO 21 连接到 1kΩ(R1)和 2kΩ(R2)电阻的交汇处。这个连接在 F21 孔上的插图中有显示。
-
将 HC-SR04 传感器的 Echo 端子连接到 1kΩ电阻(R1)。这个连接在 J17 孔上有显示。
-
将 5 伏特电源的正端连接到右侧电源轨的正电源。
-
将 5 伏特电源的负端连接到右侧电源轨的负电源。
如前所述,我们的电路构建将适用于 HC-SR04 和 HC-SR04P 模块。如果您有 HC-SR04P 模块,可以选择更简单的接线选项。由于 HC-SR04P 将在 3.3 伏特下工作,因此您可以这样做:
-
将 Vcc 连接到 3.3 伏特电源或树莓派上的 3.3 伏特引脚。
-
将 ECHO 端子直接连接到 GPIO 21。
-
GND 仍然连接到 GND,TRIG 仍然直接连接到 GPIO 20。
由于此配置以 3.3 伏特供电,因此 ECHO 端子上的逻辑输出也是 3.3 伏特,因此可以安全地直接连接到树莓派的 GPIO 引脚。
太好了!现在我们的电路已经完成,接下来我们将运行我们的示例程序,并使用 HC-SR04 来测量距离,并了解使其发生的代码。
运行和探索 HC-SR04 示例代码
HC-SR04 的示例代码可以在chapter11/hc-sr04.py文件中找到。在继续之前,请查看源代码,以对该文件的内容有一个广泛的了解。
在 HC-SR04 前面放一个实物体(大约 10 厘米),并在终端中运行代码。当您将物体靠近或远离传感器时,终端中打印的距离将会改变,如下所示:
(venv) python hc-sr04.py
Press Control + C to Exit
9.6898cm, 3.8149"
9.7755cm, 3.8486"
10.3342cm, 4.0686"
11.5532cm, 4.5485"
12.3422cm, 4.8591"
...
让我们来审查代码。
首先,在第 1 行定义了TRIG_GPIO和ECHO_GPIO引脚,在第 2 行定义了声速的VELOCITY常数。我们使用 343 米每秒。
我们的代码使用 343 米/秒作为声速,而数据表建议的值为 340 米/秒。您还会发现其他使用略有不同数值的 HC-SR04 示例和库。这些差异是不同代码示例和库可能会对相同的传感器到物体距离产生略有不同读数的原因之一。
在第 3 行,我们定义了TIMEOUT_SECS = 0.1。0.1的值大于 38 毫秒(来自数据表)。任何大于这个值的时间,我们都会得出结论,我们的 HC-SR04 传感器前面没有物体,并返回SENSOR_TIMEOUT值,而不是get_distance_cms()函数中的距离,我们马上就会讲到:
TRIG_GPIO = 20 # (1)
ECHO_GPIO = 21
# Speed of Sound in meters per second
# at 20 degrees C (68 degrees F)
VELOCITY = 343 # (2)
# Sensor timeout and return value
TIMEOUT_SECS = 0.1 # based on max distance of 4m # (3)
SENSOR_TIMEOUT = -1
接下来,从第 4 行开始,我们找到了几个变量,用于帮助测量传感器超声脉冲的时间以及我们是否有一个成功的读数:
# For timing our ultrasonic pulse
echo_callback = None # (4)
tick_start = -1
tick_end = -1
reading_success = False
echo_callback将包含一个 GPIO 回调引用,以供稍后进行清理,而tick_start和tick_end保存了用于计算超声脉冲回波的经过时间的开始和结束时间。术语tick用于与 PiGPIO 定时函数保持一致,我们将很快讨论这一点。只有在TIMEOUT_SECS过去之前我们有一个距离读数时,reading_success才为True。
我们使用第 5 行显示的trigger()函数来启动我们的距离测量。我们在第 6 行简单地应用了数据表中的流程 - 也就是说,我们使 TRIG 引脚在 10 微秒内变为HIGH:
def trigger(): # (5)
global reading_success
reading_success = False
# Start ultrasonic pulses
pi.write(TRIG_GPIO, pigpio.HIGH) # (6)
sleep(1 / 1000000) # Pause 10 microseconds
pi.write(TRIG_GPIO, pigpio.LOW)
在第 7 行显示的get_distance_cms()函数是我们的主要函数,它通过调用trigger()来启动距离测量过程,然后在第 8 行等待,直到我们有一个成功的读数(也就是reading_success = True),或者TIMEOUT_SECS过去,此时我们返回SENSOR_TIMEOUT。在等待期间,一个名为echo_handler()的回调处理程序在后台监视ECHO_GPIO引脚以获取成功的读数。我们将在本节后面讨论echo_handler()。
def get_distance_cms() # (7)
trigger()
timeout = time() + TIMEOUT_SECS # (8)
while not reading_success:
if time() > timeout:
return SENSOR_TIMEOUT
sleep(0.01)
当我们有一个成功的读数时,我们的函数继续。在第 9 行,我们取tick_start和tick_end变量(现在已经由回声回调处理程序设置了值)并计算经过的时间。记住,我们在第 9 行将经过的时间除以 2,因为我们想要从传感器到物体的时间,而不是从传感器到物体再返回传感器的完整超声脉冲往返时间:
# ... get_distance_cms() continued
# Elapsed time in microseconds.
#Divide by 2 to get time from sensor to object.
elapsed_microseconds =
pigpio.tickDiff(tick_start, tick_end) / 2 # (9)
# Convert to seconds
elapsed_seconds = elapsed_microseconds / 1000000
# Calculate distance in meters (d = v * t)
distance_in_meters = elapsed_seconds * VELOCITY # (10)
distance_in_centimeters = distance_in_meters * 100
return distance_in_centimeters
在第 10 行,我们应用了我们之前讨论过的公式,d = v × t,来计算传感器和物体之间的距离。
接下来,在第 11 行,我们遇到了echo_handler()函数,它监视ECHO_GPIO引脚的状态变化:
def echo_handler(gpio, level, tick): # (11)
global tick_start, tick_end, reading_success
if level == pigpio.HIGH:
tick_start = tick # (12)
elif level == pigpio.LOW:
tick_end = tick # (13)
reading_success = True
根据数据表中的流程,我们捕获了在第 12 行发送脉冲时的时间,当ECHO_GPIO变为HIGH,并在第 13 行接收到脉冲回来时的时间,当ECHO_GPIO变为LOW。如果我们在超时之前(在第 8 行)检测到ECHO_GPIO为LOW,我们将reading_success = True,这样get_distance_cms()就知道我们有一个有效的读数。
最后,我们在第 14 行使用 PiGPIO 注册了echo_handler()回调函数。pigpio.EITHER_EDGE参数表示我们希望在ECHO_GPIO转换为HIGH或LOW状态时调用此回调函数:
echo_callback =
pi.callback(ECHO_GPIO, pigpio.EITHER_EDGE, echo_handler) # (14)
干得好!你刚刚连接、测试和学习了如何使用 HC-SR04 传感器以及 PiGPIO 来估算距离。你刚刚学到的电路和代码示例可以被改编并用于测量水箱水位,甚至作为机器人的碰撞检测(这是 HC-SR04 在业余机器人中非常常见的应用),或者在任何其他需要距离的项目中。
接下来,我们将简要探讨霍尔效应传感器,并学习它们如何用于检测运动和相对距离。
使用霍尔效应传感器检测运动和距离
本章的最后一个实际示例将说明霍尔效应传感器的使用。霍尔效应传感器是简单的组件,用于检测磁场的存在(或不存在)。与 PIR 或距离传感器相比,您可以使用霍尔效应传感器与磁铁一起监测小范围甚至非常快速的运动。例如,您可以将一个小磁铁固定在直流电机的轴上,并使用霍尔效应传感器来确定电机的每分钟转数。
霍尔效应传感器的另一个常见应用是在手机和平板电脑中。一些手机和平板电脑的外壳和套子中有一个小磁铁。当您打开或关闭外壳时,您的设备会通过霍尔效应传感器检测到这个磁铁的存在或不存在,并自动为您打开或关闭显示屏。
霍尔效应传感器有三种类型,如下所述:
-
非锁定开关类型(数字):它们在磁场存在时输出数字状态(即
高或低),在磁场不存在时输出相反的数字状态。信号在磁场存在时是高还是低取决于传感器是主动低还是主动高(如果需要关于主动低和主动高概念的复习,请参考第六章,软件工程师的电子学 101)。 -
锁定开关类型(数字):当检测到磁铁的一个极性(例如南极)时,它们输出(并锁定到)
低(或高),当检测到另一个极性(例如北极)时返回到高(或`低)(解锁)。 -
比率类型(模拟):它们根据它们离磁场有多近而输出不同的电压。
一些读者可能熟悉一种叫做磁簧开关的组件,它是一种磁控开关。乍一看,它们在基本原理和操作上似乎与非锁定霍尔效应传感器相似。以下是重要的区别 - 与经典的磁簧开关不同,霍尔效应传感器是固态设备(没有活动部件),它们可以非常非常快地切换/触发(每秒数千次),并且它们需要一个适当的电路来使它们工作。
我们的示例将使用 A3144(非锁定数字开关)和 AH3503(模拟比率)霍尔效应传感器。由于这些特定部件的可用性和低成本,我们选择了这些特定部件;但是,我们将讨论的一般原则也适用于其他霍尔效应传感器。
图中显示了 A3144 霍尔效应传感器和常见的原理图符号:
图 11.9 - 霍尔效应传感器和符号
您会注意到最右边的符号有四个突出的输出,因为一些霍尔效应传感器确实有四条腿。您可以期望该符号的输出在适用于所指的传感器的原理图中被注释。我们将坚持使用三条腿的传感器和相应的三个输出符号。
我们组件的腿如下:
-
Vcc:5 伏电源。
-
GND:接地连接。
-
输出:5 伏信号输出。请注意,A3144 是主动
低的,这意味着在磁场存在时,输出腿变为低。
输出腿的行为将取决于霍尔效应传感器的类型:
-
锁定和非锁定开关类型:输出腿将输出数字
低或数字高。 -
比率类型:输出将是变化的电压(即模拟输出)。请注意,变化电压的范围不会是 0 到 5 伏之间的全部范围,而更可能是几百分之几伏的范围。
现在我们了解了霍尔效应传感器的腿配置,让我们构建我们的电路。
创建霍尔效应传感器电路
我们将在面包板上构建以下电路。与我们的 HC-SR04 示例和图 11.5中的电路类似,由于我们的霍尔效应传感器输出 5 伏逻辑,我们需要使用电压分压器将其降至 3.3 伏:
图 11.10 - 霍尔效应传感器电路
您会注意到该电路的输出是二元的,并且取决于您使用的传感器:
-
对于非锁定开关或锁定开关类型的霍尔效应传感器,您将直接将电路连接到 GPIO 21,因为传感器将输出数字
高/低信号。 -
对于比率类型的霍尔效应传感器,您需要通过 ADS1115 模数转换器将传感器连接到您的树莓派,因为传感器输出变化的模拟电压。
我没有在图 11.9或以下的步进面包板布局中包括 ADS1115 的接线。我们已经在之前的章节中看到了如何使用 ADS1115 将模拟输出连接到树莓派 - 例如电路和代码,请参考第五章,将您的树莓派连接到物理世界,和/或第九章,测量温度、湿度和光照水平。
让我们在面包板上构建这个电路。这个布局是用于开关型霍尔效应传感器的:
图 11.11 - 霍尔效应传感器电路面包板布局
以下是完成面包板构建的步骤。步骤编号与图 11.10中的黑色圆圈中的数字相匹配:
-
将您的 A3144 霍尔效应传感器放入面包板中,注意其腿部的方向。如果需要帮助识别元件的腿部,请参考图 11.8。
-
将 1kΩ电阻(R1)放入面包板中。
-
将 2kΩ电阻(R2)放入面包板中。这第二个电阻的一个腿与第一个电阻的一个腿共用一行。在插图中,这可以在左侧银行的第 17 行看到。
-
将树莓派的 5V 引脚连接到左侧电源轨道的正极。
-
将树莓派的 GND 引脚连接到左侧电源轨道的负极。
-
将霍尔效应传感器的 Vcc 腿连接到正电源轨道。
-
将霍尔效应传感器的 GND 腿连接到负电源轨道。
-
将霍尔效应传感器的 Out 腿连接到 1kΩ电阻(R1)。在插图中,这显示在 E13 孔。
-
将 1kΩ(R1)和 2kΩ(R2)电阻的交汇处连接到树莓派的 GPIO 21。
-
将 2kΩ电阻(R2)的左侧连接到负电源轨道。
要在这个电路中使用 AH3503 比率型霍尔效应传感器,在步骤 1,步骤 9的电线将需要连接到 ADS1115 模块的输入端口(例如 A0)。
现在我们已经建立了霍尔效应传感器电路,准备好一个磁铁,因为我们准备运行示例代码,看看磁铁如何触发传感器。
运行和探索霍尔效应传感器代码
您可以在chapter11/hall_effect_digital.py文件中找到开关和锁定开关类型霍尔效应传感器的代码,以及chapter11/hall_effect_analog.py文件中找到比率型霍尔效应传感器的代码。
当您查看这两个文件时,您会发现以下内容:
-
chapter11/hall_effect_digital.py在功能上与我们在本章前面介绍的 PIR 代码示例相同,标题为运行和探索 PIR 传感器代码。PIR 和非锁定/锁定霍尔效应传感器都是数字开关。唯一的区别是我们的参考霍尔效应传感器是活动LOW。 -
chapter11/hall_effect_analog.py类似于我们在使用 ADS1115 ACD 的其他模拟到数字示例中看到的,包括来自第五章,将您的树莓派连接到物理世界的电路布线和代码。
AH3503 比率型霍尔效应传感器输出的变化电压范围,并通过电压分压器由您的 ADC 测量,可能在几百毫伏的范围内。
当您运行示例代码时,将磁铁移过霍尔效应传感器。磁铁需要靠近传感器的外壳;然而,它不需要实际接触传感器。有多近取决于您的磁铁的强度。
如果您无法使电路和代码正常工作,请尝试旋转磁铁以改变通过传感器的南/北极。还要注意,对于闸锁型霍尔效应传感器,一个磁铁极常常会锁定(触发)传感器,而另一个磁铁极则会解锁(取消触发)传感器。
由于代码相似性,我们不会在这里再次介绍代码。但是,我想说的是,现在在本书中,您已经可以连接并使用任何简单的模拟或数字元件的数字和模拟基础电路和代码。正如本章已经指出的那样,只需注意所需的电压和电流来为元件供电,特别是输出电压是多少,因为如果超过 3.3 伏,您将需要使用电压分压器或电平转换器。
总结
在本章中,我们探讨了如何使用树莓派检测运动并估计距离。我们学会了如何使用 PIR 传感器检测广泛的运动,以及如何使用开关型霍尔效应传感器来检测磁场的运动。我们还发现了如何使用超声波测距传感器在较大范围上估计绝对距离,以及如何使用比例型霍尔效应传感器在小范围上测量相对距离。
本章中所有的电路和示例都是输入为主 - 告诉我们的树莓派发生了某些事件,比如检测到有人移动或正在测量距离。
现在你已经处于一个很好的位置,可以将本章中涵盖的输入电路(还有第九章中的内容,测量温度、湿度和光照),与第七章中的输出电路和示例,打开和关闭设备,第八章,灯光、指示灯和信息显示,以及第十章,使用舵机、电机和步进电机进行运动,结合起来,创建可以控制和测量环境的端到端项目!
不要忘记我们在第二章中学到的内容,使用 Python 和物联网入门,第三章,使用 Flask 进行 RESTful API 和 Web Sockets 网络,以及第四章,使用 MQTT、Python 和 Mosquitto MQTT Broker 进行网络。这三章为您提供了创建网页界面和集成到外部系统的基础,可以控制和监测环境。
到目前为止,在本书中呈现的许多电子和代码示例都围绕着单个传感器或执行器发展。在下一章中,我们将探索几种基于 Python 的设计模式,这些模式在构建涉及多个需要相互通信的传感器和/或执行器的更复杂的自动化和物联网项目时非常有用。
问题
最后,这里有一些问题供您测试对本章材料的了解。您将在本书的评估部分找到答案:
-
PIR 传感器能否检测物体移动的方向?
-
有哪些因素会影响超声波距离传感器的测量精度?
-
闸锁型或非闸锁型霍尔效应传感器的输出与比例霍尔效应传感器的输出有何不同?
-
关于这个 PiGPIO 函数调用,
callback = pi.callback(GPIO, pigpio.EITHER_EDGE, callback_handler),pigpio.EITHER_EDGE参数是什么意思? -
在由 1kΩ和 2kΩ电阻组成的 5 伏到 3.3 伏基于电阻的电压分压器中,为什么在电路中连接两个电阻值的方式很重要?
-
HC-SR04 超声波距离传感器和 HC-SR501 PIR 传感器都使用 5 伏电压连接到它们各自的 Vcc 引脚。为什么我们要使用电压分压器将 HC-SR04 的输出从 5 伏降到 3.3 伏,而不是 HC-SR501?
第十二章:高级 IoT 编程概念-线程、异步 IO 和事件循环
在上一章中,我们学习了如何使用 PIR 传感器检测运动,以及如何使用超声波传感器和霍尔效应传感器测量距离和检测运动。
在本章中,我们将讨论在处理电子传感器(输入设备)和执行器(输出设备)时,构建Python 程序的替代方式。我们将首先介绍经典的事件循环编程方法,然后转向更高级的方法,包括在 Python 中使用线程、发布者/订阅者模型,最后是使用 Python 进行异步 IO 编程。
我向您保证,互联网上有很多博客文章和教程涵盖了这些主题;然而,本章将专注于实际的电子接口。本章的方法将涉及创建一个简单的电路,其中包括一个按钮、一个电位计和两个 LED,我们将使它们以不同的速率闪烁,并提供四种不同的编码方法来使电路工作。
以下是本章将涵盖的内容:
-
构建和测试我们的电路
-
探索事件循环的方法
-
探索线程化方法
-
探索发布者-订阅者的替代方案
-
探索异步 IO 的方法
技术要求
为了完成本章的练习,您需要以下内容:
-
树莓派 4 型 B
-
Raspbian OS Buster(带桌面和推荐软件)
-
最低 Python 版本 3.5
这些要求是本书中代码示例的基础。可以合理地期望,只要您的 Python 版本是 3.5 或更高,本书中的代码示例应该可以在树莓派 3 型 B 或不同版本的 Raspbian OS 上无需修改即可运行。
您可以在 GitHub 存储库的chapter12文件夹中找到本章的源代码,该存储库位于github.com/PacktPublishing/Practical-Python-Programming-for-IoT。
需要在终端中执行以下命令来设置虚拟环境并安装本章所需的 Python 库:
$ cd chapter12 # Change into this chapter's folder
$ python3 -m venv venv # Create Python Virtual Environment
$ source venv/bin/activate # Activate Python Virtual Environment
(venv) $ pip install pip --upgrade # Upgrade pip
(venv) $ pip install -r requirements.txt # Install dependent packages
以下依赖项已从requirements.txt中安装:
-
PiGPIO:PiGPIO GPIO 库(
pypi.org/project/pigpio) -
ADS1X15:ADS1x15 ADC 库(
pypi.org/project/adafruit-circuitpython-ads1x15) -
PyPubSub:进程内消息和事件(
pypi.org/project/PyPubSub)
本章练习所需的电子元件如下:
-
2 x 红色 LED
-
2 x 200 Ω 电阻
-
1 x 按钮开关
-
1 x ADS1115 模块
-
1 x 10k Ω 电位计
为了最大限度地提高您在本章中的学习效果,对于预先存在的知识和经验做出了一些假设:
-
从电子接口的角度来看,我假设您已经阅读了本书前面的 11 章,并且对本书中始终出现的 PiGPIO 和 ADS1115 Python 库的工作感到满意。
-
从编程的角度来看,我假设您已经掌握了面向对象编程(OOP)技术以及它们在 Python 中的实现。
-
熟悉事件循环、线程、发布者-订阅者和同步与异步范式的概念也将是有利的。
如果前述任何主题对您来说是陌生的,您会发现有很多在线教程详细介绍了这些主题。请参阅本章末尾的进一步阅读部分以获取建议。
构建和测试我们的电路
我将以实际练习的形式呈现本章的电路和程序。让我们假设我们被要求设计和构建一个具有以下要求的小玩意:
-
它有两个 LED 灯在闪烁。
-
电位计用于调整 LED 的闪烁速率。
-
程序启动时,两个 LED 将以由电位计位置确定的相同速率闪烁。
-
0 秒的闪烁速率意味着 LED 关闭,而 5 秒的最大闪烁速率意味着 LED 打开 5 秒,然后关闭 5 秒,然后重复循环。
-
按下按钮用于选择调整闪烁速率的 LED,当调整电位计时。
-
当按下并保持按下按钮 0.5 秒时,所有 LED 将同步到相同的速率,由电位计的位置确定。
-
理想情况下,程序代码应该很容易扩展,以支持更多 LED,而编码工作量很小。
以下是一个说明使用这个小玩意的场景:
-
应用电源后(程序启动),所有 LED 以 2.5 秒的速率开始闪烁,因为电位计的刻度在旋转的中点(50%)。
-
用户调整电位计,使第一个LED 以 4 秒的速率闪烁。
-
接下来,用户简短地按下并释放按钮,以便电位计改变第二个LED 的闪烁速率。
-
现在,用户调整电位计,使第二个LED 以 0.5 秒的速率闪烁。
-
最后,用户按下并保持按钮 0.5 秒,使第一个和第二个LED 以 0.5 秒的速率同步闪烁(由步骤 4中电位计设置的速率)。
现在是我提到的挑战 - 在我们进入本章的电路和代码之前,我挑战您停止阅读,尝试创建一个实现上述要求的电路并编写程序。
您可以在youtu.be/seKkF61OE8U上找到演示这些要求的短视频。
我预计您会遇到挑战,并对采取的最佳方法有疑问。没有最佳方法;然而,通过拥有自己的实现 - 无论是否有效 - 您将有东西可以与我在本章中将提出的四种解决方案进行比较和对比。我相信,如果您首先自己尝试一下,那么您将获得更深入的理解和更多的见解。也许您会创造出更好的解决方案!
如果您需要建议来帮助您入门,这里有一些建议:
-
我们在《使用 Python 和物联网入门》的[第二章]中首次介绍了 LED 和按钮。
-
我们首先在《将树莓派连接到物理世界》的[第五章]中介绍了电位计和模拟输入,使用了 ADS1115 模块。
当您准备好时,我们将看一个满足上述要求的电路。
构建参考电路
在图 12.1中是一个符合我们刚列出的要求的电路。它有一个按钮,一个电位计,以电压分压器的形式连接到 ADS1115 模数转换器,和两个通过限流电阻连接的 LED。添加额外的 LED 将像在 GND 和一个空闲的 GPIO 引脚之间布线更多的 LED 和电阻对一样简单:
图 12.1 - 参考电路原理图
如果您还没有在面包板上创建类似的电路,我们现在将在您的面包板上创建这个电路。我们将分三部分构建这个电路。让我们开始吧:
图 12.2 - 参考电路(3 部分之一)
以下是创建我们的面包板构建的第一部分的步骤。步骤编号与图 12.2中黑色圆圈中的数字相匹配:
-
将 ADS1115 模块放入面包板中。
-
将电位计放入面包板中。
-
将 LED 放入面包板中,注意 LED 的引脚方向如图所示。
-
将第二个 LED 放入面包板中,注意 LED 的引脚方向如图所示。
-
将一个 200Ω电阻(R1)放入您的面包板中。这个电阻的一端与步骤 3中放置的 LED 的阳极腿共用一行。
-
将另一个 200Ω电阻(R2)放入您的面包板中。这个电阻的一端与步骤 5中放置的第二个 LED 的阳极腿共用一行。
-
将按键放入您的面包板中。
现在我们已经将组件放入面包板中,让我们开始将它们连接起来:
图 12.3 - 参考电路(2/3 部分)
以下是继续进行面包板组装的步骤。步骤编号与图 12.3中的黑色圆圈中的编号相匹配:
-
将树莓派的 3.3 伏特引脚连接到左侧电源轨的正电源轨。
-
将 ADS1115 的 Vdd 端连接到左侧电源轨的正电源轨。
-
将 ADS1115 的 GND 端连接到左侧电源轨的负电源轨。
-
将 ADS1115 的 SCL 端连接到树莓派的 SCL 引脚。
-
将 ADS1115 的 SDA 端连接到树莓派的 SDA 引脚。
-
将树莓派上的 GND 引脚连接到左侧电源轨的负电源轨。
-
将电位器的外端连接到左侧电源轨的正电源轨。
-
将电位器的另一个外端连接到左侧电源轨的负电源轨。
-
将电位器的中间端口连接到 ADS1115 的 A0 端口。
您是否记得,这种配置中的电位器正在创建一个可变电压分压器?如果没有,您可能需要重新阅读第六章,软件工程师的电子学 101。此外,如果您想对 ADS1115 模块进行详细复习,请参阅第五章,将树莓派连接到物理世界。
让我们继续组装:
图 12.4 - 参考电路(3/3 部分)
以下是继续进行面包板组装的最后部分的步骤。步骤编号与图 12.4中的黑色圆圈中的编号相匹配:
-
将树莓派的 GPIO 26 连接到 200Ω电阻(R1)。
-
将树莓派的 GPIO 19 连接到第二个 200Ω电阻(R2)。
-
将树莓派的 GPIO 21 连接到按键的一腿。
-
将两个 LED 的阴极腿连接在一起。
-
将 LED 的阴极腿连接到左侧电源轨的负电源轨。
-
将按键的第二腿连接到左侧电源轨的负电源轨。
现在我们已经完成了电路组装,我们准备运行示例代码使电路工作。
运行示例
本章提供了四个不同版本的代码,可以与图 12.1中先前显示的电路配合使用。您将在chapter12文件夹中按版本组织的代码:
-
chapter12/version1_eventloop是一个基于事件循环的示例。 -
chapter12/version2_thread是一个基于线程和回调的示例。 -
chapter12/version3_pubsub是一个基于发布者-订阅者的示例。 -
chapter12/version4_asyncio是一个异步 IO(AsyncIO)*-based 示例。
所有版本在功能上是等效的;但是它们在代码结构和设计上有所不同。在测试电路后,我们将更详细地讨论每个版本。
以下是运行每个版本(从版本 1 开始)并测试电路的步骤:
-
切换到
version1_eventloop文件夹。 -
简要查看
main.py源文件,以及文件夹中的任何其他 Python 文件,了解它们包含的内容以及程序的结构。 -
在终端中运行
main.py(记得先切换到本章的虚拟环境)。
此时,如果您收到关于 I2C 或 ADS11x5 的错误,请记住有一个i2cdetect工具,可以用来确认 I2C 设备(如 ADS1115)是否正确连接并对您的树莓派可见。有关更多信息,请参阅第五章,将您的树莓派连接到物理世界。
-
转动电位器拨号并观察第一个LED 的闪烁速率变化。
-
短按按钮。
-
转动电位器拨号并观察第二个LED 的闪烁速率变化。
-
按住按钮 0.5 秒,观察两个 LED 现在以相同速率同步闪烁。
以下是您将收到的终端输出示例:
(venv) $ cd version1_eventloop
(venv) $ python main.py
INFO:Main:Version 1 - Event Loop Example. Press Control + C To Exit.
INFO:Main:Setting rate for all LEDs to 2.5
INFO:Main:Turning the Potentiometer dial will change the rate for LED #0
INFO:Main:Changing LED #0 rate to 2.6
INFO:Main:Changing LED #0 rate to 2.7
INFO:Main:Turning the Potentiometer dial will change the rate for LED #1
INFO:Main:Changing LED #1 rate to 2.6
INFO:Main:Changing LED #1 rate to 2.5
# Truncated
INFO:Main:Changing LED #1 rate to 0.5
INFO:Main:Changing rate for all LEDs to 0.5
-
在终端中按*Ctrl *+ C退出程序。
-
对
version2_threads、version3_pubsub和version4_asyncio重复步骤 1至8。
您刚刚测试并浏览了四种不同程序的源代码(也许五种,如果您挑战自己创建了自己的程序),它们都以不同的方式实现了完全相同的最终结果。
现在是时候了解这些程序是如何构建的了。让我们从程序的事件循环版本开始。
探索事件循环方法
我们将通过讨论基于事件循环的方法来开始我们的代码探索,以构建我们在上一节中测试过的示例小玩意。
基于事件循环的方法的代码可以在chapter12/version1_eventloop文件夹中找到。您会找到一个名为main.py的文件。现在请花时间停下来阅读main.py中的代码,以基本了解程序的结构和工作原理。或者,您可以在代码中添加断点或插入print()语句,然后再次运行它以了解其工作原理。
您的体验如何,您注意到了什么?如果您认为呸或者在循环、if语句和状态变量的网络中迷失了,那么干得好!这意味着您已经投入了时间来考虑这种方法以及代码的构造方式。
我所说的事件循环方法在代码中通过第 1 行的while True:循环进行了演示:
# chapter12/version1_eventloop
#
# Setup and initialization code goes before while loop.
#
if __name__ == "__main__":
# Start of "Event Loop"
while True: # (1)
#
# ... Main body of logic and code is within the while loop...
#
sleep(SLEEP_DELAY)
当然,我可以使用函数甚至外部类来减少while循环中的代码数量(可能还可以增强可读性),但是,总体设计范式仍然是相同的-程序控制的主体部分处于永久循环中。
如果您熟悉 Arduino 编程,您将对这种编程方法非常熟悉。这就是为什么我将本节标题为事件循环,因为这种方法和术语的流行度相似。尽管如此,请注意事件循环这个术语在 Python 中有更广泛的上下文,当我们查看程序的 AsyncIO(版本 4)时会看到。
您可能已经意识到,本书中许多示例都使用了这种事件循环编程方法。以下是三个示例:
-
当我们需要定时事件,比如闪烁 LED 时(第二章,使用 Python 和物联网入门)
-
DHT 11 或 DHT 22 温度/湿度传感器的轮询(第九章,测量温度、湿度和光照水平)
-
轮询连接到光敏电阻(LDR)的 ADS1115 模拟数字转换器(也第九章,测量温度、湿度和光照水平)
在这种情况下,对于一个单一的专注示例,事件循环是有意义的。甚至在你进行试验和学习新的执行器或传感器时,它们也是纯粹为了方便而有意义的。然而,正如我们的version1_eventloop/main.py程序所示,一旦你添加了多个组件(比如电位计、两个 LED 和一个按钮)并且想要让它们为一个明确的目的一起工作,代码就会迅速变得复杂。
例如,考虑一下第 3 行的以下代码,它负责让所有 LED 闪烁,并记住这个代码块在每次循环迭代中被评估一次,负责让每个 LED 闪烁:
#
# Blink the LEDs.
#
now = time() # (3)
for i in range(len(LED_GPIOS)):
if led_rates[i] <= 0:
pi.write(LED_GPIOS[i], pigpio.LOW) # LED Off.
elif now >= led_toggle_at_time[i]:
pi.write(LED_GPIOS[i], not pi.read(LED_GPIOS[i])) # Toggle LED
led_toggle_at_time[i] = now + led_rates[i]
与纯粹的替代方案相比(类似于我们将在其他方法中看到的),一眼看去,它们显然更容易理解:
while True:
pi.write(led_gpio, not pi.read(led_gpio)) # Toggle LED GPIO High/Low
sleep(delay)
如果你再考虑一下从第 2 行开始的以下代码块,它负责检测按钮按下,那么你会发现在实际的main.py文件中有将近 40 行代码,只是为了检测按钮的操作:
while True:
button_pressed = pi.read(BUTTON_GPIO) == pigpio.LOW # (2)
if button_pressed and not button_held:
# Button has been pressed.
# ... Truncated ...
elif not button_pressed:
if was_pressed and not button_held:
# Button has been released
# ... Truncated ...
if button_hold_timer >= BUTTON_HOLD_SECS and not button_held:
# Button has been held down
# ... Truncated ...
# ... Truncated ...
你会发现有多个变量在起作用 - button_pressed、button_held、was_pressed和button_hold_timer - 它们在每次while循环迭代中都被评估,并且主要用于检测按钮按住事件。我相信你会理解,像这样编写和调试这样的代码可能会很乏味和容易出错。
我们本可以使用PiGPIO 回调来处理while循环之外的按钮按下,或者使用 GPIO Zero 的Button类。这两种方法都有助于减少按钮处理逻辑的复杂性。同样,也许我们本可以混合使用 GPIO Zero 的LED类来处理 LED 的闪烁。然而,如果这样做,我们的示例就不会是一个纯粹基于事件循环的示例。
现在,我并不是说事件循环是一种不好或错误的方法。它们有它们的用途,是必需的,实际上,每当我们使用while循环或其他循环结构时,我们都会创建一个 - 所以基本理念无处不在,但这并不是构建复杂程序的理想方法,因为这种方法使它们更难理解、维护和调试。
每当你发现你的程序正在走这条事件循环的道路时,停下来反思一下,因为也许是时候考虑重构你的代码,采用不同的 - 更易维护的 - 方法,比如线程/回调方法,我们将在下面看到。
探索线程方法
现在我们已经探索了一个基于事件循环的方法来创建我们的程序,让我们考虑一种使用线程、回调和面向对象编程的替代方法,并看看这种方法如何改进了代码的可读性和可维护性,并促进了代码的重用。
基于线程的方法的代码可以在chapter12/version2_threads文件夹中找到。你会找到四个文件 - 主程序main.py和三个类定义:LED.py、BUTTON.py和POT.py。
现在请花点时间停下来阅读main.py中包含的代码,以基本了解程序的结构和工作原理。然后,继续查看LED.py、BUTTON.py和POT.py。
它是如何进行的,你注意到了什么?我猜想你会发现这个程序的版本(在阅读main.py时)更快更容易理解,并且注意到没有繁琐复杂的while循环,而是一个pause()调用,这是必要的,用于阻止我们的程序退出,如第 3 行总结的那样:
# chapter12/version2_threads/main.py
if __name__ == "__main__": # (3)
# Initialize all LEDs
# ... Truncated ...
# No While loop!
# It's our BUTTON, LED and POT classes and the
# registered callbacks doing all the work.
pause()
在这个程序示例中,我们使用了面向对象的技术,并使用了三个类来组件化我们的程序:
-
一个按钮类(
BUTTON.py),负责所有按钮逻辑 -
一个电位计类(
POT.py),负责所有电位计和模拟数字转换逻辑 -
一个 LED 类(
LED.py),负责让单个LED 闪烁
通过使用面向对象的方法,我们的main.py代码大大简化了。它的作用现在是创建和初始化类实例,并包含使我们的程序工作的回调处理程序和逻辑。
考虑一下我们的按钮的面向对象的方法:
# chapter12/version2_threads/main.py
# Callback Handler when button is pressed, released or held down.
def button_handler(the_button, state):
global led_index
if state == BUTTON.PRESSED: # (1)
#... Truncated ...
elif state == BUTTON.HOLD: # (2)
#... Truncated
# Creating button Instance
button = BUTTON(gpio=BUTTON_GPIO,
pi=pi,
callback=button_handler)
与事件循环示例中的按钮处理代码相比,这大大简化了并且更易读——很明显这段代码在第 1 行响应按钮按下,第 2 行响应按钮保持。
让我们考虑一下BUTTON类,它在BUTTON.py文件中定义。这个类是一个增强的包装器,可以将按钮的 GPIO 引脚的HIGH/LOW状态转换为PRESSED、RELEASED和HOLD事件,如在BUTTON.py的第 1 行中总结的代码所示:
# chapter12/version2_threads/BUTTON.py
def _callback_handler(self, gpio, level, tick): # PiGPIO Callback # (1)
if level == pigpio.LOW: # level is LOW -> Button is pressed
if self.callback: self.callback(self, BUTTON.PRESSED)
# While button is pressed start a timer to detect
# if it remains pressed for self.hold_secs
timer = 0 # (2)
while (timer < self.hold_secs) and not self.pi.read(self.gpio):
sleep(0.01)
timer += 0.01
# Button is still pressed after self.hold_secs
if not self.pi.read(self.gpio):
if self.callback: self.callback(self, BUTTON.HOLD)
else: # level is HIGH -> Button released
if self.callback: self.callback(self, BUTTON.RELEASED)
与事件循环示例中的按钮处理代码相比,我们没有引入和审问多个状态变量来检测按钮保持事件,而是将这个逻辑简化为在第 2 行的简单线性方法。
接下来,当我们考虑POT类(在POT.py中定义)和LED类(在LED.py中定义)时,我们将看到线程进入我们的程序。
您知道即使在多线程的 Python 程序中,也只有一个线程在活动吗?虽然这似乎违反直觉,但这是 Python 语言最初创建时做出的一个称为全局解释器锁(GIL)的设计决定。如果您想了解更多关于 GIL 和使用 Python 实现并发的其他形式的信息,您可以在本章的进一步阅读部分找到相关资源。
以下是POT类的线程运行方法,可以在POT.py源文件中找到,从第 1 行开始说明了中间轮询 ADS1115 ADC 以确定电位器位置的方法。我们已经在本书中多次看到这个轮询示例,最早是在第五章中,将您的树莓派连接到物理世界,我们首次讨论模数转换、ADS1115 模块和电位器:
# chapter12/version2_threads/POT.py
def run(self):
while self.is_polling: # (1)
current_value = self.get_value()
if self.last_value != current_value: # (2)
if self.callback:
self.callback(self, current_value) # (3)
self.last_value = current_value
timer = 0
while timer < self.poll_secs: # Sleep for a while
sleep(0.01)
timer += 0.01
# self.is_polling has become False and the Thread ends.
self.__thread = None
我们这里的代码不同之处在于我们正在监视 ADC 上的电压变化(例如,当用户转动电位器时),并将其转换为回调(在第 3 行),您在审查该文件中的源代码main.py时会看到。
现在让我们讨论一下我们如何实现version2 LED 相关的代码。正如您所知,闪烁 LED 的基本代码模式涉及while循环和sleep语句。这就是 LED 类中采用的方法,如LED.py中第 3 行的run()方法中所示:
# chapter12/version2_threads/LED.py
def run(self): # (3)
""" Do the blinking (this is the run() method for our Thread) """
while self.is_blinking:
# Toggle LED On/Off
self.pi.write(self.gpio, not self.pi.read(self.gpio))
# Works, but LED responsiveness to rate chances can be sluggish.
# sleep(self.blink_rate_secs)
# Better approach - LED responds to changes in near real-time.
timer = 0
while timer < self.blink_rate_secs:
sleep(0.01)
timer += 0.01
# self.is_blinking has become False and the Thread ends.
self._thread = None
我相信您会同意这比我们在前一节讨论的事件循环方法更容易理解。然而,重要的是要记住,事件循环方法是在单个代码块中使用和改变所有LED 的闪烁速率,并在单个线程——程序的主线程中进行的。
请注意前面代码中显示的两种睡眠方法。虽然使用 sleep(self.blink_rate_secs) 的第一种方法很常见且诱人,但需要注意的是它会阻塞线程,使其在整个睡眠期间无法立即响应速率变化,当用户转动电位器时会感觉迟钝。第二种方法,称为 #Better approach,缓解了这个问题,使 LED 能够(近乎)实时地响应速率变化。
我们的version2程序示例使用 LED 类及其自己的内部线程,这意味着我们现在有多个线程——每个 LED 一个——都独立地使 LED 独立地闪烁。
你能想到这可能引入的任何潜在问题吗?好吧,如果你已经阅读了version2源文件,这可能是显而易见的——当按钮按下 0.5 秒时,同步所有 LED 以同样的速率同时闪烁!
通过引入多个线程,我们引入了多个定时器(即sleep()语句),因此每个线程都在自己独立的时间表上闪烁,而不是从一个共同的参考点开始闪烁。
这意味着,如果我们简单地在多个 LED 上调用led.set_rate(n),虽然它们都会以速率n闪烁,但它们不一定会同步闪烁。
解决这个问题的一个简单方法是在开始以相同速率闪烁之前同步关闭所有 LED。也就是说,我们从一个共同的状态(即关闭)开始让它们一起闪烁。
这种方法在LED.py的第 1 行开始的以下代码片段中显示。同步的核心是在第 2 行的led._thread.join()语句中实现的:
# chapter12/version2_threads/LED.py
@classmethod # (1)
def set_rate_all(cls, rate):
for led in cls.instances: # Turn off all LEDs.
led.set_rate(0)
for led in cls.instances:
if led._thread:
led._thread.join() # (2)
# We do not get to this point in code until all
# LED Threads are complete (and LEDS are all off)
for led in cls.instances: # Start LED's blinking
led.set_rate(rate)
这是同步的一个很好的第一步,对于我们的情况来说,实际上效果很好。正如前面提到的,我们所做的就是确保我们的 LED 从关闭状态同时开始闪烁(嗯,非常非常接近同时,取决于 Python 迭代for循环所花费的时间)。
尝试将前面代码中第 2 行的led._thread.join()和包含的for循环注释掉,然后运行程序。让 LED 以不同的速率闪烁,然后尝试通过按住按钮来同步它们。它总是有效吗?
但必须指出的是,我们仍然在处理多个线程和独立的定时器来让我们的 LED 闪烁,因此存在时间漂移的可能性。如果这曾经成为一个实际问题,那么我们将需要探索替代技术来同步每个线程中的时间,或者我们可以创建并使用一个单一的类来管理多个 LED(基本上使用事件循环示例中的方法,只是将其重构为一个类和一个线程)。
关于线程的要点是,当您将线程引入应用程序时,您可能会引入可能可以设计或同步的时间问题。
如果你的原型或新程序的第一次尝试涉及基于事件循环的方法(就像我经常做的那样),那么当你将代码重构为类和线程时,始终要考虑可能出现的任何时间和同步问题。在测试期间意外发现与同步相关的错误(或更糟糕的是,在生产中)是令人沮丧的,因为它们很难可靠地复制,并且可能导致需要进行大量的重做。
我们刚刚看到了如何使用面向对象编程技术、线程和回调创建样本小工具程序。我们已经看到了这种方法导致了更容易阅读和维护的代码,同时也发现了需要同步线程代码的额外要求和工作。接下来,我们将看一下我们的程序的第三种变体,它是基于发布-订阅模型的。
探索发布-订阅的替代方法
现在我们已经看到了使用线程、回调和面向对象编程技术创建程序的方法,让我们考虑第三种方法,使用发布-订阅模型。
发布-订阅方法的代码可以在chapter12/version3_pubsub文件夹中找到。你会找到四个文件——主程序main.py和三个类定义:LED.py、BUTTON.py和POT.py。
现在请花时间停下来阅读main.py中包含的代码,以基本了解程序的结构和工作原理。然后,继续查看LED.py、BUTTON.py和POT.py。
你可能已经注意到,整体程序结构(特别是类文件)与我们在上一个标题中介绍的version2线程/回调示例非常相似。
你可能也意识到,这种方法在概念上与 MQTT 采用的发布者/订阅者方法非常相似,我们在第四章中详细讨论了 MQTT、Python 和 Mosquitto MQTT Broker 的网络。主要区别在于,在我们当前的version3示例中,我们的发布者-订阅者上下文仅限于我们的程序运行时环境,而不是网络分布式程序集,这是我们 MQTT 示例的情况。
我已经使用PyPubSub Python 库在version3中实现了发布-订阅层,该库可以从pypi.org获取,并使用pip安装。我们不会详细讨论这个库,因为这种类型的库的整体概念和使用应该已经很熟悉了,如果没有,我相信一旦你审查了version3源代码文件(如果你还没有这样做),你会立刻明白发生了什么。
Python 通过 PyPi.org 提供了其他可选的 PubSub 库。选择在这个例子中使用PyPubSub是因为它的文档质量和提供的示例。你会在本章开头的技术要求部分找到这个库的链接。
由于version2(线程方法)和version3(发布者-订阅者方法)示例的相似性,我们不会详细讨论每个代码文件,只是指出核心差异:
-
在
version2(线程)中,这是我们的led、button和pot类实例之间的通信方式: -
我们在
main.py上注册了button和pot类实例的回调处理程序。 -
button和pot通过这种回调机制发送事件(例如按钮按下或电位器调整)。 -
我们直接使用
set_rate()实例方法和set_rate_all()类方法与 LED 类实例进行交互。 -
在“version3”(发布者-订阅者)中,这是类内通信结构和设计:
-
每个类实例都是非常松散耦合的。
-
没有回调。
-
在类实例创建并注册到
PyPubSub之后,我们不再直接与任何类实例进行交互。 -
所有类和线程之间的通信都是使用
PyPubSub提供的消息层进行的。
现在,说实话,我们的小玩意程序并不从发布者-订阅者方法中受益。我个人偏好采用回调版本来处理这样一个小程序。然而,我提供了发布者-订阅者的替代实现作为参考,这样你就有这个选择来考虑你自己的需求。
发布者-订阅者方法在更复杂的程序中表现出色,其中有许多组件(这里指的是软件组件,不一定是电子组件)需要共享数据,并且可以以异步的发布者-订阅者方式进行。
我们在本章中以四个非常离散和专注的例子来展示编码和设计方法。然而,在实践中,当创建程序时,通常会将这些方法(以及其他设计模式)以混合的方式结合起来。记住,使用的方法或方法组合应该是对你所要实现的目标最有意义的。
正如我们刚刚讨论过的,你在审查version3代码时会看到,我们的小玩意程序的发布者-订阅者方法是线程和回调方法的一个简单变体,我们不再直接使用回调与类实例交互,而是将所有代码通信标准化到一个消息层。接下来,我们将看看我们编写小玩意程序的最终方法,这次采用 AsyncIO 方法。
探索 AsyncIO 方法
到目前为止,在本章中,我们已经看到了三种不同的编程方法来实现相同的最终目标。我们的第四种和最终方法将使用 Python 3 提供的 AsyncIO 库构建。正如我们将看到的,这种方法与我们以前的方法有相似之处和不同之处,并且还为我们的代码及其操作方式增加了一个额外的维度。
根据我的经验,第一次体验 Python 中的异步编程可能会感到复杂、繁琐和令人困惑。是的,异步编程有一个陡峭的学习曲线(在本节中我们只能勉强触及表面)。然而,当您学会掌握这些概念并获得实际经验时,您可能会开始发现这是一种优雅而优美的创建程序的方式!
如果您是 Python 中异步编程的新手,您将在进一步阅读部分找到精心策划的教程链接,以加深您的学习。在本节中,我打算为您提供一个专注于电子接口的简单工作的 AsyncIO 程序,您可以在学习更多关于这种编程风格时作为参考。
基于异步的方法的代码可以在chapter12/version4_asyncio文件夹中找到。您会找到四个文件 - 主程序main.py和三个类定义:LED.py,BUTTON.py和POT.py。
现在请花时间停下来阅读main.py中包含的代码,以基本了解程序的结构和工作原理。然后继续查看LED.py,BUTTON.py和POT.py。
如果您也是 JavaScript 开发人员 - 特别是 Node.js - 您可能已经知道 JavaScript 是一种异步编程语言;但是,它看起来和感觉起来与您在 Python 中看到的非常不同!我可以向您保证,原则是相同的。以下是它们感觉非常不同的一个关键原因 - JavaScript 是默认异步的。正如任何有经验的 Node.js 开发人员所知道的,我们经常不得不在代码中采取(通常是极端的)措施来使我们的代码部分表现出同步行为。对于 Python 来说,情况正好相反 - 它是默认同步的,我们需要额外的编程工作来使我们的代码部分表现出异步行为。
当您阅读源代码文件时,我希望您将我们的version4 AsyncIO 程序视为同时具有version1基于事件循环的程序和version2线程/回调程序的元素。以下是关键差异和相似之处的摘要:
-
整体程序结构与
version2线程/回调示例非常相似。 -
在
main.py的末尾,我们有几行新的代码,在这本书中我们以前没有见过 - 例如,loop = asyncio.get_event_loop()。 -
像
version2程序一样,我们使用了面向对象编程技术将组件分解为类,这些类也有一个run()方法 - 但请注意这些类中没有线程实例,也没有与启动线程相关的代码。 -
在类定义文件
LED.py,BUTTON.py和POT.py中,我们在run()函数中使用了async和await关键字,并在while循环中延迟了 0 秒 - 也就是说,asyncio.sleep(0)- 因此我们实际上并没有睡觉! -
在
BUTTON.py中,我们不再使用 PiGPIO 回调来监视按钮被按下,而是在while循环中轮询按钮的 GPIO。
Python 3 的 AsyncIO 库随着时间的推移发生了显著的演变(并且仍在演变),具有新的 API 约定,更高级功能的添加和废弃的函数。由于这种演变,代码可能会很快地与最新的 API 约定过时,两个代码示例展示了相同的基本概念,但可能使用看似不同的 API。我强烈建议您浏览最新的 Python AsyncIO 库 API 文档,因为它将为您提供有关新旧 API 实践的提示和示例,这可能有助于您更好地解释代码示例。
我将通过以简化的方式引导您了解程序的高级程序流程来解释这个程序是如何工作的。当您能够掌握正在发生的一般情况时,您就已经在理解 Python 中的异步编程方面迈出了重要的一步。
您还会发现一个名为chapter12/version4_asyncio/main_py37.py的文件。这是我们程序的 Python 3.7+版本。它使用自 Python 3.7 以来可用的 API。如果您浏览这个文件,差异是清楚地被注释了。
在main.py文件的末尾,我们看到以下代码:
if __name__ == "__main__":
# .... truncated ....
# Get (create) an event loop.
loop = asyncio.get_event_loop() # (1)
# Register the LEDs.
for led in LEDS:
loop.create_task(led.run()) # (2)
# Register Button and Pot
loop.create_task(pot.run()) # (3)
loop.create_task(button.run()) # (4)
# Start the event loop.
loop.run_forever() # (5)
Python 中的异步程序围绕着事件循环发展。我们在第 1 行创建了这个事件循环,并在第 5 行启动了它。我们将在稍后回到在第 2、3 和 4 行之间发生的注册。
这个异步事件循环的整体原则与我们的 version1 事件循环示例类似;但是,语义是不同的。两个版本都是单线程的,两组代码都会在循环中运行。在version1中,这是非常明确的,因为我们的主要代码体包含在外部的while循环中。在我们的异步version4中,这更加隐含,并且有一个核心的区别——如果编写正确,它是非阻塞的,并且很快我们会看到,这是类run()方法中await asyncio.sleep()调用的目的。
正如前面提到的,我们已经在第 2、3 和 4 行将我们的类run()方法注册到循环中。在第 5 行启动事件循环后,简化来看发生了以下情况:
- 第一个LED 的
run()函数(在下面的代码中显示)被调用:
# version4_asyncio/LED.py
async def run(self):
""" Do the blinking """
while True: # (1)
if self.toggle_at > 0 and
(time() >= self.toggle_at): # (2)
self.pi.write(self.gpio, not self.pi.read(self.gpio))
self.toggle_at += self.blink_rate_secs
await asyncio.sleep(0) # (3)
-
它进入第 1 行的
while循环,并根据闪烁速率从第 2 行切换 LED 的开关状态。 -
接下来,它到达第 3 行,
await asyncio.sleep(0),并让出控制。在这一点上,run()方法实际上被暂停了,另一个while循环迭代不会开始。 -
控制权转移到第二个LED 的
run()函数,并且它通过它的while循环运行一次,直到达到await asyncio.sleep(0)。然后它让出控制。 -
现在,pot 实例的
run()方法(在下面的代码中显示)获得了运行的机会:
async def run(self):
""" Poll ADC for Voltage Changes """
while True:
# Check if the Potentiometer has been adjusted.
current_value = self.get_value()
if self.last_value != current_value:
if self.callback:
self.callback(self, current_value)
self.last_value = current_value
await asyncio.sleep(0)
-
run()方法执行while循环的一个迭代,直到达到await asyncio.sleep(0)。然后它让出控制。 -
控制权转移到
button实例的run()方法(部分显示在下面的代码中),它有多个await asyncio.sleep(0)语句:
async def run(self):
while True:
level = self.pi.read(self.gpio) # LOW(0) or HIGH(1)
# Waiting for a GPIO level change.
while level == self.__last_level:
await asyncio.sleep(0)
# ... truncated ...
while (time() < hold_timeout_at) and \
not self.pi.read(self.gpio):
await asyncio.sleep(0)
# ... truncated ...
await asyncio.sleep(0)
-
一旦按钮的
run()方法达到任何await asyncio.sleep(0)的实例,它就会让出控制。 -
现在,我们所有注册的
run()方法都有机会运行,所以第一个LED 的run()方法将再次控制并执行一个while循环迭代,直到达到await asyncio.sleep(0)。同样,在这一点上它让出控制,第二个LED 的run()方法再次获得运行的机会…这个过程一遍又一遍地继续进行,每个run()方法以轮流的方式获得运行的机会。
让我们解决一些可能会有问题的问题:
- 那么按钮的
run()函数和它的许多await asyncio.sleep(0)语句呢?
当在任何await asyncio.sleep(0)语句处让出控制时,函数就在这一点上让出。下一次run()按钮获得控制时,代码将从await asyncio.sleep(0)语句下面的下一个语句继续执行。
- 为什么睡眠延迟为 0 秒?
等待零延迟睡眠是放弃控制的最简单方法(请注意,这是asyncio库的sleep()函数,而不是time库的sleep()函数)。然而,你可以await任何异步方法,但这超出了我们简单示例的范围。
我在这个例子中使用了零秒延迟,以简化解释程序的工作原理,但你也可以使用非零延迟。这只是意味着放弃控制的run()函数会在这段时间内休眠 - 直到这段时间过去,事件循环才会让它运行。
- 那么
async和await关键字呢?我怎么知道在哪里使用它们?
这当然需要练习;然而,这里有一些基本的设计规则:
-
- 如果你要向事件循环注册一个函数(例如
run()),那么这个函数必须以async关键字开头。
- 如果你要向事件循环注册一个函数(例如
-
任何
async函数必须包含至少一个await语句。
编写和学习异步程序需要练习和实验。你将面临的一个最初的设计挑战是知道在哪里放置await语句(以及有多少个),以及你应该放弃控制多长时间。我鼓励你玩一下version4代码库,添加你自己的调试print()或日志语句,然后进行实验和调试,直到你对它如何组合在一起有了感觉。在某个时候,你会有那个“啊哈”时刻,那时,你刚刚打开了进一步探索 Python AsyncIO 库提供的许多高级功能的大门。
现在我们已经看到了异步程序在运行时的结构和行为,我想给你一些可以进行实验和思考的东西。
异步实验
让我们试一试。也许你想知道为什么version4(AsyncIO)有点像我们的version1(事件循环)代码,只是它已经重构成类,就像version2(线程)代码一样。那么,我们是否可以将version1 while循环中的代码重构成类,创建并调用一个函数(例如run())在while循环中,而不必理会所有的异步内容及其额外的库和语法?
让我们试试。你会在chapter12/version5_eventloop2文件夹中找到一个与此类似的版本。尝试运行这个版本,看看会发生什么。你会发现第一个 LED 会闪烁,第二个 LED 会一直亮着,按钮和电位器不起作用。
你能想出原因吗?
简单的答案是:在main.py中,一旦第一个 LED 的run()函数被调用,我们就会永远停留在它的while循环中!
调用sleep()(来自time库)不会放弃控制;它只是在下一个while循环迭代发生之前暂停 LED 的run()方法。
因此,这就是为什么我们说同步程序是阻塞的(不会放弃控制),而异步程序是非阻塞的(它们放弃控制并让其他代码有机会运行)的一个例子。
希望你喜欢我们探索了四种不同的构建电子接口程序的方法,以及我们不应该使用的方法。让我们通过回顾本章学到的内容来结束。
总结
在本章中,我们看了四种不同的 Python 程序与电子设备接口的结构方式。我们了解了一种基于事件循环的编程方法,两种基于线程的变体 - 回调和发布-订阅模型 - 最后看了一下异步编程的工作方式。
我们讨论的四个例子都在方法上非常具体和离散。虽然我们在讨论过程中简要讨论了每种方法的相对优势和缺点,但值得记住的是,在实践中,你的项目可能会使用这些方法的混合(可能还有其他方法),这取决于你试图实现的编程和接口目标。
在下一章中,我们将把注意力转向物联网平台,并讨论可用于构建物联网程序的各种选项和替代方案。
问题
最后,这里有一些问题供您测试本章内容的知识。您可以在书的“评估”部分找到答案:
-
发布者-订阅者模型何时是一个好的设计方法?
-
Python GIL 是什么,对于经典线程有什么影响?
-
为什么纯事件循环通常不适合复杂的应用程序?
-
事件循环方法是一个坏主意吗?为什么?
-
thread.join()函数调用的目的是什么? -
您已经使用线程通过模拟数字转换器来轮询您的新模拟组件。然而,您发现您的代码对组件的变化反应迟缓。可能的问题是什么?
-
在 Python 中设计物联网或电子接口应用的优越方法是什么——使用事件循环、线程/回调、发布者-订阅者模型还是基于 AsyncIO 的方法?
进一步阅读
realpython.com网站提供了一系列优秀的教程,涵盖了 Python 中的并发编程,包括以下内容:
-
Python GIL 是什么?
realpython.com/python-gil -
通过并发加速您的 Python 程序:
realpython.com/python-concurrency -
Python 中线程的介绍:
realpython.com/intro-to-python-threading -
Python 中的异步 IO:完整演练:
realpython.com/async-io-python
以下是来自官方 Python(3.7)API 文档的相关链接:
-
AsyncIO 库:
docs.python.org/3.7/library/asyncio.htm -
使用 AsyncIO 进行开发:
docs.python.org/3.7/library/asyncio-dev.html -
Python 中的并发编程:
docs.python.org/3.7/library/concurrency.html
第十三章:物联网可视化和自动化平台
在上一章中,我们探讨了与电子设备接口的 Python 程序结构的替代方法。这包括事件循环方法,两种基于线程的方法,显示回调和发布-订阅模型的使用,以及异步 I/O 方法。
在本章中,我们将讨论您可以与树莓派一起使用的物联网和自动化平台。术语物联网平台和自动化平台是非常广泛的概念,因此在本章中,我所指的是任何软件服务-基于云或本地安装-为您提供一个现成的生态系统,以创建强大,灵活和有趣的物联网项目。
我们的主要重点将放在If-This-Then-That(IFTTT)自动化平台上,我怀疑你们中的许多人对此都有一些了解,并且 ThingSpeak 平台用于数据可视化。我选择了这两个服务,因为它们都提供免费的定价层,并且允许我们创建和探索简单的演示和示例,您可以在此基础上构建。但是,除此之外,我还将讨论一些我有经验的其他物联网和自动化平台,这些平台将使您能够构建更强大的物联网解决方案。
本章将涵盖以下主题:
-
从树莓派触发 IFTTT Applet
-
从 IFTTT Applet 操作您的树莓派
-
使用 ThingSpeak 平台可视化数据
-
其他物联网和自动化平台供进一步探索
让我们开始吧!
技术要求
要执行本章的练习,您将需要以下物品:
-
树莓派 4 型 B
-
Raspbian OS Buster(带桌面和推荐软件)
-
Python 版本至少为 3.5
这些要求是本书中代码示例的基础。可以合理地期望代码示例应该可以在树莓派 3 型 B 或不同版本的 Raspbian OS 上无需修改即可工作,只要您的 Python 版本是 3.5 或更高。
您将在本书的 GitHub 存储库的chapter13文件夹中找到本章的源代码,该存储库位于此处:github.com/PacktPublishing/Practical-Python-Programming-for-IoT。
您需要在终端中执行以下命令来设置虚拟环境并安装本章代码所需的 Python 库:
$ cd chapter13 # Change into this chapter's folder
$ python3 -m venv venv # Create Python Virtual Environment
$ source venv/bin/activate # Activate Python Virtual Environment
(venv) $ pip install pip --upgrade # Upgrade pip
(venv) $ pip install -r requirements.txt # Install dependent packages
以下依赖项将从requirements.txt中安装:
-
PiGPIO:PiGPIO GPIO 库(
pypi.org/project/pigpio) -
Paho MQTT 库:
pypi.org/project/paho-mqtt -
Requests HTTP 库:
pypi.org/project/requests -
基于 PiGPIO 的 DHT 库:
pypi.org/project/pigpio-dht
本章练习所需的电子元件如下:
-
1 x DHT11(较低精度)或 DHT22(较高精度)温湿度传感器
-
1 x 红色 LED
-
电阻:
-
1 x 200Ω 电阻
-
1 x 10kΩ 电阻(可选)
从树莓派触发 IFTTT Applet
你们中的许多人可能已经熟悉If-This-Than-That(IFTTT)网络服务(ifttt.com),在那里您可以创建称为Applets的简单工作流自动化链。Applet 响应一个网络服务的更改(This),然后触发另一个网络服务的操作(That)。
以下是一些 Applet 配置(称为*Recipes)的常见示例:
-
每当特定的 Twitter 标签发布时,向自己发送电子邮件。
-
在一天的特定时间打开或关闭智能灯泡。
-
当您接近您的房子时,使用手机的 GPS 打开您的联网车库门。
-
在电子表格中记录您在办公室的时间。
-
…以及成千上万的其他示例!
正如我们将在本节和下一节中学到的那样,我们的 Raspberry Pi 可以承担This或That的角色,以触发 Applet 或响应触发的 Applet 执行操作。
以下是我们将在本节中涵盖的内容的可视化表示;即,使我们的 Raspberry Pi 承担 IFTTT 工作流程中This角色:
图 13.1 - Raspberry Pi 在 IFTTT Applet 工作流程中承担This角色
我们即将介绍的 Python 示例将监视当前温度(This),并在特定温度时请求特殊的 IFTTT Webhook URL。此 URL 请求将触发我们的 Applet,然后发送电子邮件(That)。在构建我们的第一个 IFTTT Applet 时,我们将很快更详细地讨论 Webhooks。
首先,我们需要创建和测试我们的示例电路,接下来我们将这样做。
创建温度监测电路
在本示例中,我们将重用我们在第九章中创建的 DHT11/DHT22 温度电路,测量温度、湿度和光照水平。
我们需要做的是:
-
构建图 9.2中所示的电路。
-
将数据引脚连接到 GPIO 24(在第九章中,测量温度、湿度和光照水平,我们使用了 GPIO 21,但我们将在本章后面使用 GPIO 21 来控制 LED)。
一旦您建立了电路,我们就可以继续并构建我们的第一个 IFTTT Applet。
创建和配置 IFTTT Applet
要创建我们的 IFTTT Applet,我们需要遵循许多步骤。这些步骤中的许多步骤都很简单和通用,无论您创建的 Applet 类型如何。虽然我们将逐步介绍这些通用步骤,但我们不会详细介绍它们,因为我相信您完全能够理解在过程中发生了什么。相反,我们将专注于与集成我们的 Raspberry Pi 相关的 IFTTT 的独特步骤和部分。
请注意,ifttt.com/免费定价层限制了您可以同时拥有的 Applet 数量。在撰写本文时,最大值为三个活动 Applet。在本章和下一章中,我们将创建四个 Applet,因此您需要在进行下一章时至少将一个 Applet 存档到 IFTTT 上,以保持在 IFTTT 免费定价层上。
以下是我们需要遵循的步骤:
- 登录或创建您的 IFTTT 帐户。如果您还没有 IFTTT 帐户,请访问ifttt.com/join并按照屏幕上的说明操作。
我们在 IFTTT 网站ifttt.com上执行这些步骤。IFTTT 手机和平板应用程序的操作流程将不同。
- 登录到 IFTTT 后,点击您的个人资料头像图标(在下图中用方框标出)以显示菜单:
图 13.2 - 个人资料头像图标
- 接下来,点击个人资料菜单中的创建选项,如下所示:
图 13.3 - 个人资料菜单
- 接下来您将看到的页面是创建您自己的页面。在这里,点击If和This之间的+图标:
图 13.4 - 创建您自己的页面 - 第一部分
- 现在,您将被要求选择一个服务。我们需要选择的服务与我们的 Raspberry Pi 集成的服务称为 WebHook 服务,如下所示:
图 13.5 - 选择服务页面
-
找到并识别 Webhook 服务后,点击 Webhooks 图标继续。
-
接下来您将看到的页面是选择触发器页面,如下截图所示。在这里,点击接收 Web 请求选项:
图 13.6 - 选择触发器页面
- 接下来,您将看到完成触发字段页面,如下所示:
图 13.7 - 完成触发字段页面
事件名称对于我们的树莓派集成非常重要。在我们即将介绍的 Python 代码中,我们必须确保代码中使用的事件名称与我们在此页面中输入的名称匹配。在我们的示例中,我们将事件命名为 RPITemperature。
- 在“事件名称”框中输入 RPITemperature,然后点击“创建触发器”按钮继续。
Webhook 的事件名称是其唯一标识符(用于您的 IFTTT 帐户)。如果您创建了许多 Webhooks,则需要使用不同的事件名称来区分它们。
- 接下来,您将再次看到“创建您自己”的页面。这一次,您会发现This现在是 Webhook 图标:
图 13.8 - 创建您自己的页面 - 第二部分
我们现在已经完成了配置 IFTTT Applet 的一半。现在我们已经配置了 Webhook 触发器,我们需要配置我们的动作,即发送电子邮件。创建电子邮件动作后,我们将重新访问 Webhook 触发器,并发现用于触发此 Webhook 事件的 URL 和参数。
- 接下来,在“然后”和“那个”之间点击“+”图标。您将看到选择动作服务页面。在此页面上,搜索电子邮件并点击电子邮件图标:
图 13.9 - 选择动作服务页面
- 当您看到下图所示的选择动作页面时,请选择“发送电子邮件”选项:
图 13.10 - 选择动作页面
- 接下来,您将看到完成动作字段页面。请填写主题和正文文本字段,如下截图所示。您将在本章后面找到此动作生成的示例电子邮件:
图 13.11 - 完成动作字段页面
在前面的屏幕截图中,您会注意到一些文本被灰色框包围;例如,“Value1”和“OccuredAt”。这些被称为ingredients,在触发 Applet 时会动态替换。正如我们很快将在代码中看到的那样,我们将用当前温度、湿度和消息分别替换 Value1、Value2 和 Value3。
-
填写主题和正文文本字段后,点击“创建动作”按钮。
-
最后,在审查和完成页面上点击“完成”按钮,如下所示:
图 13.12 - 审查和完成页面
恭喜!您刚刚创建了一个 IFTTT Applet,当我们使用树莓派触发它时,它会发送一封电子邮件。但是我们如何做到的呢?这就是我们将在下一节中了解的内容。
触发 IFTTT Webhook
现在我们已经创建了我们的 IFTTT Applet,我们需要采取一些步骤来学习如何触发我们的 Webhook。这些步骤归结为知道在 IFTTT 中导航到哪里以发现您的唯一 Webhook URL。
以下是我们需要遵循的步骤:
- 首先,我们需要导航到 Webhooks 页面。我们可以通过几种方式来做到这一点,我会让您自行决定采取哪种方式:
-
- 将您的网络浏览器导航到 Webhook 服务 URL;即ifttt.com/maker_webhook。
-
或者,导航到此网页的步骤如下:
-
点击个人资料头像图标(如图 13.2中所示)。
-
在出现的菜单中,选择“My Services”项目(参见图 13.3)。
-
在出现的页面上,找到并点击“Webhooks”项目。
无论您选择哪条路径,您将看到下图所示的页面:
图 13.13 - Webhooks 页面
- 单击页面右上角的 Documentation 按钮。您将看到这里显示的 Webhook 文档页面:
图 13.14 - Webhook 文档页面请注意,在前面的示例页面中,我已经填写了{Event}和 JSON Body 字段,以便在我们的讨论中引用它们。您的字段将最初为空。
这个页面包含了我们需要的关键信息,以便将这个 Webhook 触发与我们的 Raspberry Pi 集成。这个页面的关键部分如下:
-
您的密钥:这是您的帐户的 Webhook API 密钥,并且是您独特的 Webhook URL 的一部分。
-
GET 或 POST 请求 URL:您独特的 Webhook URL。您的 API 密钥和事件名称的独特组合是将 URL 与可触发的 IFTTT 事件关联起来的。要与我们的 Raspberry Pi 集成,这是我们需要构建和请求的 URL。我们将很快在代码中介绍这一点。
-
事件名称:您想触发的事件的名称。
-
JSON 主体:每个可触发的 Webhook 可以包含最多三个以 JSON 格式呈现的数据参数,它们必须命名为 value1、value2 和 value3。
-
cURL 命令行示例:在终端中运行此示例以触发 RPITemperature 事件(您将收到一封电子邮件)。
-
测试按钮:单击此按钮将触发 RPITemperature 事件(您将收到一封电子邮件)。
现在我们已经创建了 IFTTT Applet,并发现了在哪里找到 Webhook URL 以及它是如何构建的,我们现在可以深入研究将触发我们的 IFTTT Applet 的 Python 代码。
在 Python 中触发 IFTTT Applet
我们将要探索一个简单的应用程序,基于我们在第九章中首次看到的 DHT 11/DHT 22 电路和代码,测量温度、湿度和光照水平。您可以在chapter13/ifttt_dht_trigger_email.py文件中找到这段代码。
这段代码将使用 DHT 11 或 DHT 22 传感器监视温度,如果违反了预先配置的高温或低温阈值,代码将调用您的 IFTTT Webhook URL,然后会像下面的截图中显示的那样给您发送一封电子邮件。这对应于您在步骤 13中配置的电子邮件主题和正文文本:
图 13.15 - 示例 IFTTT 电子邮件
在我们运行示例应用程序代码之前,我们需要执行一些配置步骤。让我们来看一下:
-
打开
chapter13/ifttt_dht_trigger_email.py文件进行编辑。 -
找到由第(1)和(2)行表示的以下代码段。确认您的 DHT 传感器连接到适当的 GPIO 引脚,并且根据您拥有的传感器使用正确的 DHT11 或 DHT22 实例:
# DHT Temperature/Humidity Sensor GPIO. GPIO = 24 # (1) # Configure DHT sensor - Uncomment appropriate line
# based on the sensor you have. dht = DHT11(GPIO, use_internal_pullup=True, timeout_secs=0.5) # (2)
#dht = DHT22(GPIO, use_internal_pullup=True, timeout_secs=0.5)
- 现在,找到以下代码段,由行(3)、(4)和(5)表示,并将
USE_DEGREES_CELSIUS,HIGH_TEMP_TRIGGER和LOW_TEMP_TRIGGER变量更新为在您的位置有意义的值:
USE_DEGREES_CELSIUS = True # False to use Fahrenheit # (3)
HIGH_TEMP_TRIGGER = 20 # Degrees # (4)
LOW_TEMP_TRIGGER = 19 # Degrees # (5)
当温度达到HIGH_TEMP_TRIGGER度或降至LOW_TEMP_TRIGGER度时,您的 IFTTT Applet 将被触发并发送电子邮件。设置高温和低温触发的原因是为了创建一个小的温度缓冲区,以防止代码在温度在单个值以上下波动时触发多封电子邮件。
- 接下来,找到从第 6 行开始的以下代码部分,并更新显示的详细信息 - 特别是您在上一节中识别的 IFTTT API 密钥,在步骤 2中:
EVENT = "RPITemperature" # (6)
API_KEY = "<ADD YOUR IFTTT API KEY HERE>"
这就是我们所有的配置。您会注意到第 7 行,这是我们使用我们的 API 密钥和事件名称构建 IFTTT Webhook URL 的地方:
URL = "https://maker.ifttt.com/trigger/{}/with/key/{}".format(EVENT, API_KEY) # (7)
文件中的其余代码轮询 DHT11 或 DHT22 传感器,将读数与HIGH_TEMP_TRIGGER和HIGH_TEMP_TRIGGER值进行比较,如果温度已超过,构造一个requests对象并调用 IFTTT Webhook URL 来触发您的 Applet。我们不会在这里涵盖该代码,因为根据您之前使用 DHT11/DHT22 传感器和 Python requests库的经验,这应该是不言自明的。
配置好我们的代码后,是时候在终端中运行程序了。您将收到类似以下的输出:
(venv) $ python ifttt_dht_trigger_email.py
INFO:root:Press Control + C To Exit.
INFO:root:Sensor result {'temp_c': 19.6, 'temp_f': 67.3, 'humidity': 43.7, 'valid': True}
INFO:root:Sensor result {'temp_c': 20.7, 'temp_f': 69.3, 'humidity': 42.9, 'valid': True}
INFO:root:Temperature 20.7 is >= 20, triggering event RPITemperature
INFO:root:Response Congratulations! You've fired the RPITemperature event
INFO:root:Successful Request.
我们的示例还显示了当温度超过 20 度时触发 IFTTT Applet。
现在,我们使用我们的树莓派在This角色中触发 IFTTT Applet 完成了我们的 IFTTT 示例。我们所涵盖的基本流程说明了实现这一点有多容易!我们发送了一封电子邮件,但您可以按照相同的整体流程创建其他触发其他操作的 IFTTT 配方,例如打开智能灯和电器,向 Google 电子表格添加行,以及创建 Facebook 帖子。您可能想要查看ifttt.com/discover以获取各种想法和可能性。请记住,从我们的角度和我们的学习来看,这是一个Webhook触发器,我们可以从我们的树莓派中使用它来实现这些想法。玩得开心!
接下来,我们将看看相反的情况,看看我们如何操作我们的树莓派。
从 IFTTT Applet 中操作您的树莓派
上一节教会了我们如何从我们的树莓派触发 IFTTT Applet。在本节中,我们将学习如何从 IFTTT Applet 中操作我们的树莓派。
对于我们的示例,我们将创建一个 IFTTT Applet,当收到电子邮件时将触发该 Applet。我们将使用此电子邮件的主题来控制连接到 GPIO 引脚的 LED。
我们将使用 IFTTT Webhook 服务,就像以前一样,只是这次 Webhook 服务将安装在我们 Applet 的That一侧,并且将请求我们指定的 URL。这个基本想法在下图中有所说明:
图 13.16 - 树莓派在 IFTTT Applet 中扮演That角色
让我们看看我们可以使用的两种可能的方法,通过 IFTTT Webhook 服务请求一个 URL,然后可以被我们的树莓派的 Python 代码看到。
方法 1 - 使用 dweet.io 服务作为中介
将 IFTTT 与我们的树莓派集成的一种方法是使用 dweet.io 服务。我们在第二章中介绍了 dweet.io 以及 Python 示例,使用 Python 和物联网入门。
简而言之,我们将如何在 IFTTT Webhook 中使用 dweet.io 以及我们的 Python 代码:
-
在我们的 IFTTT Webhook 中,我们将使用 dweet.io URL 发布一个 dweet(包含打开、关闭或使 LED 闪烁的指令)。
-
我们的树莓派将运行 Python 代码来检索 IFTTT Webhook 发布的 dweet。
-
然后,我们的代码将根据 dweet 中指定的命令控制 LED。
这是我们示例中将要使用的方法。这种方法的优势在于我们无需担心在路由器上配置防火墙和端口转发规则。此外,这意味着我们可以在工作环境等环境中运行示例,而在这些环境中,路由器配置可能不切实际甚至不可能。
我们将在chapter13/dweet_led.py文件中使用此基于 dweet.io 的集成的代码,该文件是第二章中chapter02/dweet_led.py文件的精确副本,使用 Python 和物联网入门。
方法 2 - 创建 Flask-RESTful 服务
要使用这种方法,我们需要创建一个类似于我们在第三章中所做的 RESTful 服务,使用 Flask 进行 RESTful API 和 Web 套接字服务(chapter02/flask_api_server.py中的代码,它改变 LED 的亮度(而不是设置为开/关/闪烁),将是一个很好的起点)。
我们还需要将我们的树莓派暴露到公共互联网,这将需要我们在本地防火墙或路由器中打开一个端口并创建一个端口转发规则。然后,连同我们的公共 IP(或域名),我们可以构建一个 URL,并直接将其与 IFTTT Webhook 服务一起使用。
对于原型设计和创建演示,一个简单的替代方法是使用本地隧道(localtunnel.github.io/www)或 ngrok(ngrok.com)这样的服务,而不是打开防火墙和创建端口转发规则,这些服务可以帮助您将设备暴露到互联网上。
由于这种方法需要您进行配置和设置,这超出了我们作为本章的一部分可以实际完成的范围,因此我们将坚持使用前一节中显示的 dweet.io 方法。
接下来,我们将创建一个电路,我们可以在第二个 IFTTT Applet 中使用,我们将很快构建它。
创建 LED 电路
我们即将介绍的示例将需要一个 LED,以及连接到 GPIO 引脚(对于我们的示例是 GPIO 21)的一系列电阻。我相信,考虑到我们在本书中已经多次构建了 LED 电路,你可以毫无问题地自己完成这个连接!(如果你需要提醒,请参见第二章中的图 2.7,使用 Python 和物联网入门)
保留您为我们第一个 IFTTT Applet 示例创建的 DHT 11/DHT 22 电路,因为我们将在本章后面再次重用此电路。
当您的电路准备好后,我们将继续并运行我们的示例程序。
运行 IFTTT 和 LED Python 程序
在本节中,我们将运行我们的程序,并获取用于 dweet.io 服务的唯一物名称和 URL。
以下是要遵循的步骤:
- 在终端中运行
chapter13/dweet_led.py文件中的代码。您将收到类似以下内容的输出(您的物名称和因此您的 URL 将不同):
(venv) $ python dweet_led.py
INFO:main:Created new thing name 749b5e60
LED Control URLs - Try them in your web browser:
On : https://dweet.io/dweet/for/749b5e60?state=on
Off : https://dweet.io/dweet/for/749b5e60?state=off
Blink : https://dweet.io/dweet/for/749b5e60?state=blink
正如我们之前提到的,chapter13/dweet_led.py是我们在第二章中讨论的相同程序的精确副本,使用 Python 和物联网入门。如果您需要更多关于这个程序如何工作的上下文,请重新阅读那一章和其中包含的代码讨论。
- 保持终端打开并运行程序,因为我们将需要在下一节中复制其中一个 URL。我们还需要运行程序来测试我们即将进行的集成。
接下来,我们将创建另一个 IFTTT Applet,通过 dweet.io 与该程序集成。
创建 IFTTT Applet
我们即将创建另一个 IFTTT Applet。整个过程与我们之前创建的 Applet 非常相似,只是我们的树莓派(通过 Webhook 集成)将位于 Applet 的That端,如图 13.16所示。
以下是我们需要遵循的步骤来创建我们的下一个 Applet。由于它们与我们之前创建的 IFTTT Applet 过程非常相似,这次我省略了许多常见的屏幕截图:
-
登录到 IFTTT 后,点击个人资料头像图标,然后从下拉菜单中选择创建。
-
在 If + This Then Than 页面上,点击+图标。
-
在“选择服务”页面上,搜索并选择“电子邮件”服务。
-
在选择触发器页面上,选择发送带标签的 IFTTT 电子邮件(确保选项中包含这个词*tagged)。
-
在下一页中,输入 LED 作为标签输入,然后点击创建触发器按钮:
图 13.17 - 完成触发字段页面
-
在 If This Then + Than 页面上,点击+图标。
-
在选择操作服务页面上,搜索并选择 Webhooks 服务。
-
接下来,在选择操作页面上,选择进行网络请求。
-
接下来你会遇到的页面叫做“完成操作字段”。这是我们将使用上一节中程序打印到终端的 dweet URL 的地方:
图 13.18 - 完成操作字段页面
以下是您需要遵循的子步骤,以完成此页面上的字段:
-
- 从终端复制 On URL(例如,
https://dweet.io/dweet/for/749b5e60?state=on- 注意您的thing name将不同)。
- 从终端复制 On URL(例如,
-
将此 URL 粘贴到 IFTTT URL 字段中。
-
在 URL 字段中,删除单词 on(所以 URL 现在是 https://dweet.io/dweet/for/749b5e60?state=)。
-
点击添加成分按钮(在 URL 字段下),选择主题(使 URL 现在为 https://dweet.io/dweet/for/749b5e60?state={{Subject}})。
-
其他字段可以保留为默认值。
-
点击创建操作按钮:
图 13.19 - 完成操作字段页面
- 最后,在“审查和完成”页面上,点击“完成”按钮。
干得好!我们现在创建了第二个 Applet。接下来,我们将使用这个 Applet 通过发送电子邮件来控制我们的 LED,指示 LED 打开、关闭或闪烁。
从电子邮件控制 LED
现在我们已经创建了一个 Applet 来通过电子邮件控制 LED,是时候测试集成了。
以下是创建电子邮件的步骤:
-
确保
chapter13/dweet_led.py文件中的程序仍在终端中运行。 -
打开您喜欢的电子邮件程序并创建新邮件。
-
使用
trigger@applet.ifttt.com作为电子邮件的收件人地址。
当向 IFTTT 发送触发电子邮件时,它必须来自您在 IFTTT 中使用的相同电子邮件地址(您可以访问ifttt.com/settings来检查您的电子邮件地址)。
- 作为主题,使用以下内容之一来控制 LED:
-
#LED On -
#LED Off -
#LED Blink
IFTTT 会去掉#LED 标签,因此我们的dweet_led.py程序只会收到打开、关闭或闪烁的文本。在我们的 Python 代码中,前导空格被去掉。
以下截图显示了一个使 LED 闪烁的示例电子邮件:
13.20 - 触发电子邮件示例
-
发送电子邮件。
-
等一会儿,LED 将改变状态。
现在我们已经学会了如何通过电子邮件使用 IFTTT 来控制我们的 LED,让我们快速介绍一些故障排除技巧。
IFTTT 故障排除
如果您的 IFTTT Applets 似乎没有触发和执行操作,这里有一些故障排除途径供您探索和尝试:
-
在
dweet_led.py中,尝试以下操作: -
打开调试日志记录;例如,
logger.setLevel(logging.DEBUG)。 -
更改源文件末尾附近的 dweet 检索方法。如果您正在使用
stream_dweets_forever(),请尝试改用poll_dweets_forever(),因为它对瞬时连接问题更具弹性。 -
在 IFTTT 网站上,您可以通过以下方式检查任何 Applet 的活动日志:
-
导航到个人资料菜单下的我的服务选项
-
选择一个服务(例如,Webhooks)
-
选择要检查的 Applet
-
点击设置按钮
-
点击查看活动按钮和/或尝试立即检查按钮
-
您还可以查看以下 IFTTT 资源:
-
常见错误和故障排除提示,请访问
help.ifttt.com/hc/en-us/articles/115010194547-Common-errors-and-troubleshooting-tips -
故障排除 Applets & Services,请访问
help.ifttt.com/hc/en-us/categories/115001569887-Troubleshooting-Applets-Services。
IFTTT 还有一个最佳实践页面,您可以在help.ifttt.com/hc/en-us/categories/115001569787-Best-Practices上了解更多关于该平台的信息。
在我们讨论了从树莓派触发 IFTTT Applet部分之后,对于 IFTTT 触发器,您可以采用我们刚刚介绍的相同的整体流程来执行您的树莓派,以便从任何 IFTTT 配方中采取行动。再次查看ifttt.com/discover以获取一些想法,这一次,请记住,从我们的角度来看,我们在 IFTTT 配方中使用Webhook操作来控制我们的树莓派。这里有一个例子-使用 Google 助手语音控制您的树莓派!哦,等一下-我们将在下一章第十四章中做到这一点-将所有内容联系在一起-物联网圣诞树!
我们已经探讨了如何以两种方式将我们的树莓派与 IFTTT 集成-作为This角色来触发 Applet,以及在That角色中,我们可以从触发的 Applet 中执行我们的树莓派。接下来,我们将看一种方法来创建一个物联网仪表板,我们可以用来可视化数据。
使用 ThingSpeak 平台可视化数据
我们刚刚学习了如何使用 IFTTT 平台创建简单的自动化。在本节中,我们将与 ThingSpeak 平台集成,以可视化显示我们将使用 DHT 11 或 DHT 22 传感器收集的温度和湿度数据。我们将使用我们在本章前面创建的 DHT 11/DHT 22 电路。
ThingSpeak(thingspeak.com)是一个数据可视化、聚合和分析平台。我们将专注于数据可视化方面,特别是如何将我们的树莓派集成到该平台中。
我选择在本节中以 ThingSpeak 为例的原因有几个-它简单易用,并且对于我们将要做的简单数据可视化来说,它是免费的。还有许多其他可用的可视化平台,它们都有自己独特的功能、定价结构和复杂性。我在其他物联网和自动化平台供进一步探索部分中包含了一些建议供您探索。
如果您希望更深入地探索聚合和分析功能,您可以通过搜索 ThingSpeak 找到许多优质的示例、教程和文档。作为建议,从au.mathworks.com/help/thingspeak开始您的调查。
我们将在下面的屏幕截图中看到我们将要创建的仪表板的一个示例。请注意标签栏中显示的通道设置和 API 密钥项目-我们将很快提到这些标签:
图 13.21-ThingSpeak 通道仪表板
在我们可以集成我们的树莓派并将数据发送到 ThingSpeak 之前,我们的第一站是为我们的集成配置平台。
配置 ThinkSpeak 平台
配置 ThinkSpeak 相对简单-事实上,这是我遇到的同类平台中最简单的之一。以下是我们需要遵循的步骤:
-
首先,您需要为自己创建一个 ThingSpeak 账户。访问他们的网站thingspeak.com,然后点击“注册”按钮。
-
一旦您创建了 ThinkSpeak 账户并登录到平台,您应该会登陆到“我的通道”页面;也就是
thingspeak.com/channels。
在 ThingSpeak 生态系统中,通道是我们存储数据、仪表板和可视化的虚拟位置。这类似于一个工作区。
- 接下来,我们需要点击“新通道”按钮创建一个新通道:
图 13.22-ThingSpeak 通道配置
在新通道页面上,输入以下详细信息:
-
- 名称:
环境数据(或您选择的任何名称)
- 名称:
-
字段 1:
温度 -
字段 2:
湿度
您可以将所有其他字段保留为默认值。
如果以后需要查看或更改通道设置,可以在通道设置选项卡中找到,如*图 13.19.*中所示。
- 填写完字段后,滚动到页面底部,然后单击“保存通道”按钮。您将看到一个类似于图 13.19的页面,只是没有数据,是空白的。
要在图 13.19中看到的两个表中添加两个表,请执行以下操作:
-
- 按“添加小部件”按钮。
-
选择“仪表”图标,然后按“下一步”。
-
在“配置小部件参数”对话框中,输入仪表的名称(例如
温度),并选择适当的字段编号(温度为 Field1,湿度为 Field2)。 -
您可以根据需要调整和实验其他参数,以设置仪表的最大/最小范围、颜色和其他显示属性。
-
为第二个表重复该过程。
如果仪表(或图表)显示“字段值不可用”,不要担心。这是正确的,因为我们还没有向 ThingSpeak 发送任何温度或湿度数据。
- 现在,是时候获取 API 密钥和通道 ID 了,我们需要这些信息来配置即将到来的 Python 代码。点击 API 密钥选项卡:
图 13.21 - API 密钥选项卡
这是我们的 Python 程序需要收集的信息:
-
- 写 API 密钥(因为我们将向平台写入数据)
-
通道 ID(这在所有 ThinkSpeak 页面上都有,靠近顶部)
现在我们已经创建并配置了一个简单的 ThinkSpeak 通道,并收集了我们的 API 密钥和通道 ID,我们可以继续进行 Python 代码。
配置和运行 ThinkSpeak Python 程序
我们提供了两个样本程序,可以与 ThinkSpeak 集成。它们如下:
-
chapter13/thingspeak_dht_mqtt.py:一个使用 MQTT 将数据发送到 ThinkSpeak 通道的示例。 -
chapter13/thingspeak_dht_http.py:一个使用 Python 请求库进行 RESTful API 调用,将数据发送到 ThinkSpeak 通道的示例。
这两个程序的核心概念在早期章节中已经讨论过。供您参考,它们如下:
-
MQTT:我们在第四章中讨论了 Paho-MQTT 库,使用 MQTT、Python 和 Mosquitto MQTT 代理进行网络通信。本章的一个关键区别是,我们使用 Paho-MQTT 简化客户端包装器来发布 MQTT 消息,而不是完整的生命周期示例。
-
我们在第二章中介绍了 RESTful API 和请求库,Python 和物联网入门。
-
有关 DHT 11/DHT 22 温湿度传感器的代码在第九章中已经涵盖了,测量温度、湿度和光照水平。
让我们配置这些程序,运行它们,并在 ThingSpeak 中看到数据。我们将演示chapter13/thingspeak_dht_mqtt.py中提供的示例代码;但是,对于chapter13/thingspeak_dht_http.py,整个过程都是相同的:
-
打开
chapter13/thingspeak_dht_mqtt.py文件进行编辑。 -
在文件顶部附近,找到以下代码,从第(1)行开始,并确认您的 DHT 传感器连接到正确的 GPIO 引脚,并且正确的传感器实例在代码中启用:
# DHT Temperature/Humidity Sensor
GPIO = 24 # (1)
#dht = DHT11(GPIO, use_internal_pullup=True, timeout_secs=0.5)
dht = DHT22(GPIO, use_internal_pullup=True, timeout_secs=0.5)
- 接下来,找到以下代码段,从第(2)行开始,并使用您的 ThingSpeak 写 API 密钥、通道 ID 和时区进行更新。请注意,
CHANNEL_ID仅在 MQTT 集成中使用(因此它不会出现在thingspeak_dht_http.py文件中):
# ThingSpeak Configuration
WRITE_API_KEY = "" # <<<< ADD YOUR WRITE API KEY HERE # (2)
CHANNEL_ID = "" # <<<< ADD YOUR CHANNEL ID HERE
# See for values https://au.mathworks.com/help/thingspeak/time-zones-reference.html
TIME_ZONE = "Australia/Melbourne"
- 保存文件并运行程序。您应该会收到类似以下内容的输出:
(venv) $ python thing_speak_dht_mqtt.py
INFO:root:Collecting Data and Sending to ThingSpeak every 600 seconds. Press Control + C to Exit
INFO:root:Sensor result {'temp_c': 25.3, 'temp_f': 77.5, 'humidity': 43.9, 'valid': True}
INFO:root:Published to mqtt.thingspeak.com
- 几秒钟后,您应该会在 ThingSpeak 仪表板上看到您的数据出现!
恭喜!通过这样,你已经创建了一个 ThingSpeak 仪表板来可视化树莓派收集的数据。对于许多监控物联网项目来说,可视化数据是一个经常的需求,无论是简单的指示器显示,比如表盘,还是生成历史图表来可视化趋势。你如何处理数据的可视化完全取决于你的需求;然而,所有这些需求共同的一点是,有许多现成的服务,如 ThingSpeak,可以帮助你实现这一点,而不是自己定制编码仪表板和可视化应用。
现在,我将用一个简短的讨论来结束这一章,讨论一些其他流行的物联网平台,你可能会喜欢在未来的项目中探索和使用。
其他物联网和自动化平台供进一步探索
到目前为止,在本章中,我们已经看到了 IFTTT 和 ThingSpeak 的运作方式,以及如何将它们与我们的树莓派集成。我们看到了如何使用 IFTTT 创建简单的工作流程,以及如何使用 ThingSpeak 可视化数据——两个非常不同的想法,但它们都是物联网平台。
这两个平台都非常强大,并提供了广泛的功能和可能性,超出了我们在一个章节中所能涵盖的范围,所以我鼓励你查阅它们的文档和示例,以提升你的学习。
还有许多其他可用的物联网平台、应用程序和框架。本节将根据我的经验提供一个简短的策划清单。它们都与本书的 Python 和树莓派主题很好地契合。
Zapier
我们已经看到了 IFTTT 的运作方式。在支持的服务方面,IFTTT 更加面向消费者,而且正如我们所见,我们受限于单个 This 触发器和单个 That 动作。
Zappier 在原则上与 IFTTT 非常相似,但更加注重商业,包括一系列服务和集成,这些在 IFTTT 中不可用(IFTTT 也有独特的服务和集成)。此外,Zapier 还能够触发事件和动作的更复杂工作流程。
你会发现重新实现本章中我们的两个 IFTTT 示例在 Zappier 中相对简单。
网站:zapier.com。
IFTTT 平台
在本章中,我们使用 IFTTT 作为最终用户,并使用 Webhooks 进行集成。如果你是一家希望创建作为一流 IFTTT 服务公开的小工具的企业,那么你应该了解一下 IFTTT 平台。
ThingsBoard 物联网平台
ThingsBoard 是一个开源的物联网平台,你可以在树莓派上下载和托管。从表面上看,它将允许你构建仪表板和数据可视化,就像我们在 ThingSpeak 中所做的那样。与 ThingSpeak 相比,你会发现 ThingsBoard 在创建你的第一个仪表板时有一个更陡的学习曲线;然而,你也会发现它提供了更广泛的小部件和自定义选项。此外,与只能消耗数据的 ThingSpeak 不同,ThingsBoard 允许你将控件嵌入到仪表板中,让你使用 MQTT 与你的树莓派进行交互。
根据经验,如果你想学习如何使用这个平台,那么你必须仔细阅读 ThingsBoard 的文档和教程(许多都是视频),因为在你第一次访问其用户界面时,不会立即明显你需要做什么。
以下是他们网站上的一些具体资源:
-
树莓派安装说明:
thingsboard.io/docs/user-guide/install/rpi(不用担心它说的是树莓派 3;它在 4 上也能运行)
在入门指南中没有 Python 特定的示例,但有 Mosquito MQTT 示例和 cURL 示例,演示了 RESTful API。建议使用本章中提供的两个 ThingSpeak 代码示例作为起点,并采用它们来使用 ThingBoard 特定的 MQTT 和/或 RESTful API。
网站:thingsboard.io。
Home Assistant
Home Assistant 是一个纯 Python 家庭自动化套件。Home Assistant 可以与各种互联网设备连接,如灯、门、冰箱和咖啡机 - 仅举几例。
Home Assistant 在这里得到提及,不仅因为它是用 Python 构建的,而且因为它允许我们直接与主机树莓派的 GPIO 引脚集成,以及使用 PiGPIO 的远程 GPIO 功能与远程树莓派的 GPIO 引脚集成。此外,还有 MQTT 和 RESTful API 集成选项。
虽然在概念和最终用户操作上很简单,但是在配置 Home Assistant 时存在较高的学习曲线(需要相当多的实验),因为大多数集成是通过直接编辑YAML Ain’t Markup Language(YAML)文件来完成的。
关于 GPIO 集成,我从他们的网站上选择了一些资源来帮助您入门。我建议先阅读术语表,因为这将帮助您更好地理解 Home Assistant 的术语,从而帮助您更好地理解文档的其他部分:
-
安装:Home Assistant 可以以多种方式安装。为了测试平台并构建 GPIO 集成,我建议选择“虚拟环境”选项,文档位于
www.home-assistant.io/docs/installation/virtualenv。 -
可用的树莓派集成:
www.home-assistant.io/integrations/#search/Raspberry%20Pi。
亚马逊网络服务(AWS)
另一个建议是亚马逊网络服务,具体来说是两项服务 - IoT Core 和 Elastic Beanstalk。这些选项将为您提供巨大的灵活性和几乎无穷无尽的选择,当涉及到创建物联网应用程序时。IoT Core 是亚马逊的物联网平台,您可以在其中创建仪表板、工作流和集成,而 Elastic Beanstalk 是他们的云平台,您可以在其中托管自己的程序 - 包括 Python - 在云中。
亚马逊网络服务是一个先进的开发平台,因此您需要投入几周的时间来学习它的工作原理,以及如何使用它构建和部署应用程序,但我可以向您保证,在这个过程中您会学到很多!此外,他们的文档和教程质量非常高。
亚马逊物联网核心:aws.amazon.com/iot-core。
亚马逊弹性 Beanstalk:aws.amazon.com/elasticbeanstalk。
Microsoft Azure、IBM Watson 和 Google Cloud
最后,我想提一下其他 IT 巨头,他们都提供自己的云和物联网平台。我之所以建议 AWS,纯粹是因为我对这个平台有更深入的经验。微软、IBM 和谷歌提供的比较平台也是高质量的,并且有着优秀的文档和教程支持,因此如果您个人偏好于这些提供商中的一个,您仍然是安全的。
摘要
在本章中,我们探讨并学习了如何将我们的 Raspberry Pi 与 IFTTT 和 ThinkSpeak IoT 平台一起使用。我们创建了两个 IFTTT 示例,其中我们的 Raspberry Pi 在 IFTTT Applet 中执行了This角色,以启动 IFTTT 工作流程。我们还看到了如何将我们的 Raspberry Pi 用作That角色,以便它可以被 IFTTT Applet 执行。接下来,我们介绍了如何与 ThinkSpeak IoT 平台集成,以可视化由我们的 Raspberry Pi 收集的温度和湿度数据的示例。最后,我们讨论了您可能希望调查和实验的其他 IoT 平台选项。
在本章中,我们确实只涵盖了可视化和自动化平台可能性的基础知识。我鼓励你寻找更多的 IFTTT 示例和你可以尝试的想法,并探索我们提到的其他平台。请记住,虽然每个平台都会有所不同并且有自己的集成考虑,但通常接受的实现集成的标准是 RESTful API 和 MQTT,这两者你现在都有经验了!
在下一章中,我们将涵盖一个全面的端到端示例,汇集了本书中涵盖的许多概念和示例。
问题
随着我们结束本章,这里有一些问题供你测试对本章材料的了解。你将在附录的评估部分找到答案:
-
在我们的第一个 IFTTT Applet 中,我们监测温度时为什么使用了不同的高温和低温值来触发我们的 Applet 并发送电子邮件?
-
使用像 dweet.io 这样的中介服务与我们的 IFTTT Webhook 服务有什么优势?
-
IFTTT 和 Zapier 之间的一些核心区别是什么?
-
你能从 ThingSpeak 仪表板控制你的 Raspberry Pi 吗?
-
关于数据,当 IFTTT Webhook 服务用作动作(即 applet 的That一侧)时有什么限制?
-
你想要原型化基于 Raspberry Pi 的 GPIO 引脚状态来开关柜台智能灯泡。你可以使用哪些平台?
第十四章:将所有内容联系在一起-物联网圣诞树
欢迎来到我们的最后一章!我们将通过整合前几章的各种主题和想法来完成本书,以构建一个多方面的物联网程序。具体来说,我们将构建一个可以通过互联网控制的圣诞树,一个IoTree,如果你不介意的话!
本章中我们的方法是重用前几章的两个电路,以创建圣诞树灯光(使用 APA102 LED 灯带)和一个摇摆机制来使树摇晃(我们将使用舵机)和发出叮当声(好吧,如果你用铃铛装饰树,它会在摇晃时发出叮当声!)。然后我们将重新审视和调整我们关于 RESTful API 和 MQTT 的学习,以创建两种方式来通过网络或互联网控制灯光和舵机。然后我们将重新审视 dweet.io 和If-This-Then-That(IFTTT)并构建 IFTTT Applets 来通过电子邮件和您的声音使用 Google 助手来控制树!
以下是本章将涵盖的内容:
-
物联网圣诞树概述
-
构建 IoTree 电路
-
配置、运行和使用 Tree API 服务
-
配置、运行和使用 Tree MQTT 服务
-
将 IoTree 与 dweet.io 集成
-
与电子邮件和 Google 助手通过 IFTTT 集成
-
扩展您的 IoTree 的想法和建议
技术要求
要执行本章的练习,您需要以下内容:
-
树莓派 4 Model B
-
Raspbian OS Buster(带桌面和推荐软件)
-
最低 Python 版本 3.5
这些要求是本书中代码示例的基础。可以合理地期望,只要您的 Python 版本是 3.5 或更高,代码示例应该可以在树莓派 3 Model B 或不同版本的 Raspbian OS 上无需修改地工作。
要完成标题为与 Google 助手集成的部分,至少需要以下先决条件:
-
一个 Google 账户(如果您有 Gmail 邮箱账户,那就是您需要的全部)
-
安卓手机或 iOS 的Google 助手应用
您将在 GitHub 存储库的chapter14文件夹中找到本章的源代码,链接在这里:github.com/PacktPublishing/Practical-Python-Programming-for-IoT。
您需要在终端中执行以下命令来设置虚拟环境并安装本章代码所需的 Python 库:
$ cd chapter14 # Change into this chapter's folder
$ python3 -m venv venv # Create Python Virtual Environment
$ source venv/bin/activate # Activate Python Virtual Environment
(venv) $ pip install pip --upgrade # Upgrade pip
(venv) $ pip install -r requirements.txt # Install dependent packages
以下依赖项从requirements.txt中安装:
-
PiGPIO:PiGPIO GPIO 库(
pypi.org/project/pigpio) -
Flask-RESTful:用于创建 RESTful API 服务的 Flask 扩展(
pypi.org/project/Flask-RESTful) -
Paho MQTT 客户端:
pypi.org/project/paho-mqtt -
Pillow:Python Imaging Library(PIL)(
pypi.org/project/Pillow) -
Luma LED Matrix 库:
pypi.org/project/luma.led_matrix -
Requests:用于发出 HTTP 请求的高级 Python 库(
pypi.org/project/requests) -
PyPubSub:进程内消息传递和事件(
pypi.org/project/PyPubSub)
本章练习所需的电子元件如下:
-
1 x MG90S 爱好舵机(或等效的 3 线、5 伏特爱好舵机)
-
1 x APA102 RGB LED 灯带
-
1 x 逻辑电平转换模块
-
外部电源供应(至少是 3.3V/5V 面包板可安装的电源供应)
视频展示了这棵树的运作情况,网址是youtu.be/15Xfuf_99Io。请注意,这棵树使用 RGB LED 和交替闪烁的灯光动画。在本章中,我们将使用 APA102 LED 灯带,它能够创建更多的动画效果。演示树还可以演奏曲调,但我们在本章不会涉及(尽管您可以轻松地通过采用第八章中的 RTTTL 示例来添加该功能)。
IoT 圣诞树概述
在我们通过构建电路和查看代码来开始本章之前,让我们花一点时间了解一下我们的 IoTree 将会做什么以及我们将如何构建它。图 14.1中的树代表了您在完成本章后可能创建的东西:
图 14.1 - IoTree 示例
现在,我需要提前告诉您,我们只会涵盖 IoTree 的电子和编程部分。您需要发挥自己的主动性,并发挥您的制造技能来建造这棵树并让它活起来。我建议使用一棵小桌面圣诞树,因为我们的构建部分涉及使用伺服机来摇晃树。我们的业余级伺服机足够强大,可以摇动一棵小树;但是,它不太可能能够摇动一棵全尺寸的圣诞树(如果您希望将我们的构建升级到更大的树,请研究并获得更强大的伺服机,并且如果您这样做,请给我发一张照片!)。
我们的基本树将包括以下电子组件:
-
用于树灯的 APA102 LED 灯带(我们在第八章中介绍了 APA102 LED 灯带,灯光、指示灯和信息显示)。
-
一个伺服机使树摇晃和叮当作响 - 为此,您需要在树上放一些铃铛装饰品,当树摇晃时它们会叮当作响(我们在第十章中介绍了伺服机,使用伺服机、电机和步进电机进行运动)。
在程序和结构上,我们的树程序将借鉴我们学到的以下概念:
-
dweet.io 服务:首次介绍于第二章,Python 和 IoT 入门,并在第十三章,IoT 可视化和自动化平台中重新讨论
-
使用 Flask-RESTful 的 RESTful API:来自第三章,使用 Flask 进行 RESTful API 和 Web 套接字进行网络连接
-
消息队列遥测传输(MQTT):在第四章中介绍,使用 MQTT、Python 和 Mosquitto MQTT 代理进行网络连接。
-
IoT 程序的线程和发布-订阅(PubSub)方法:在第十二章中介绍,高级 IoT 编程概念-线程、AsyncIO 和事件循环
-
IFTTT IoT 平台:在第十三章中介绍,IoT 可视化和自动化平台
在我们继续本章之前,我们将假设您已经理解了前述每一章的概念,并且已经完成了每一章中提出的练习,包括构建电路和理解使电路工作的电路和代码级概念。
我们的第一个任务将是构建 IoTree 所需的电路,接下来我们将进行。
构建 IoTree 电路
是时候开始建设了!请构建图 14.2中所示的电路:
图 14.2 - IoTree 电路原理图
这个电路应该看起来很熟悉。它是我们之前见过的两个电路的组合:
-
来自图 8.4的 APA102(带逻辑电平转换器)电路,位于第八章中,灯光、指示灯和信息显示
-
来自图 10.2的舵机电路,位于第十章中,使用舵机、电机和步进电机进行运动
如果您需要逐步构建面包板上的电路的说明,请参阅相应的章节。
请记住,您需要使用外部电源来为 APA102 和舵机供电,因为它们会吸取太多的电流,无法使用树莓派的 5 伏引脚。
当您完成电路构建后,接下来让我们简要讨论三个可以用来控制此电路的程序。
三个 IoTree 服务程序
有三个单独的程序来配合我们的 IoTree,每个程序都采用了稍微不同的方法来处理我们的灯光和舵机。这些程序如下:
-
Tree API 服务(位于
chapter14/tree_api_service文件夹中):此程序提供了一个使用 Flask-RESTful 创建的 RESTful API,用于控制灯光和舵机。它还包括一个使用 API 的基本 HTML 和 JavaScript Web 应用程序。我们将在标题为配置、运行和使用 Tree API 服务的部分进一步讨论 Tree API 服务。 -
Tree MQTT 服务(位于
chapter14/tree_mqtt_service文件夹中):此程序将允许我们通过发布 MQTT 消息来控制灯光和舵机。我们将在标题为配置、运行和使用 Tree MQTT 服务的部分进一步讨论 Tree MQTT 服务。 -
dweet 集成服务(位于
chapter14/dweet_integration_service文件夹中):此程序接收 dweets 并将它们重新发布为 MQTT 消息。我们可以将此程序与Tree MQTT 服务程序一起使用,以使用 dweet.io 来控制我们的灯光和舵机,从而为我们提供了一种将 IoTree 与 IFTTT 等服务集成的简单方法。我们将在标题为将 IoTree 与 dweet.io 集成的部分进一步讨论 dweet 集成服务。
现在我们已经简要讨论了构成本章示例的程序,让我们配置和运行我们的 Tree API 服务,并使用它来使灯光和舵机工作。
配置、运行和使用 Tree API 服务
Tree API 服务程序为控制我们的 IoTree 的 APA102 LED 灯带和舵机提供了一个 RESTful API 服务。您可以在chapter14/tree_api_service文件夹中找到 Tree API 服务程序。它包含以下文件:
-
README.md:Tree API 服务程序的完整 API 文档,包括示例。 -
main.py:这是程序的主要入口点。 -
config.py:程序配置。 -
apa102.py:与 APA102 LED 灯带集成的 Python 类。这段代码的核心与我们在第八章中探讨的 APA102 Python 代码非常相似,只是现在它被构造为 Python 类,使用线程来运行灯光动画,还有一些其他小的添加,比如让 LED 闪烁的代码。 -
apa102_api.py:提供 APA102 API 的 Flask-RESTful 资源类。它借鉴了第三章中 Flask-RESTful 代码和示例,使用 Flask 进行 RESTful API 和 Web 套接字的网络 -
servo.py:用于控制舵机的 Python 类。它借鉴了我们在第十章中介绍的舵机代码。 -
servo_api.py:提供舵机 API 的 Flask-RESTful 资源类。 -
模板:此文件夹包含示例 Web 应用程序的index.html文件。 -
static:此文件夹包含 Web 应用程序使用的静态 JavaScript 库和图像。
图 14.3 显示了 Tree API 服务程序架构的图表:
图 14.3 - Tree API 服务架构块图
这是 Tree API 服务的高级操作,用于前面图表中显示的 API 请求:
-
外部客户端向#1 处的
/lights/colors端点发出 POST 请求。 -
请求由 Flask 框架/服务器在#2 处处理。(Flask 和 Flask-RESTful 的设置可以在
main.py中找到。) -
/lights/*端点被路由到适当的 Flask-RESTful 资源#3(APA102 - 也就是light - 资源在apa102_api.py中定义)。端点设置和资源注册在main.py中找到。 -
在#4 处,调用适当的资源(在本例中,将是
ColorControl.post()),然后解析和验证查询字符串参数(即colors=red%20blue&pattern=yes)。 -
最后,在#5 处,
ColorControl.post()调用 APA102 的实例中的适当方法(在apa102.py中定义,并在main.py中设置),直接与物理 APA102 LED 灯带接口并更新重复的红色和蓝色模式。
现在我们了解了我们的 Tree API 服务的工作原理,在运行 Tree API 服务之前,首先需要检查它的配置。我们接下来会做这个。
配置 Tree API 服务
Tree API 服务配置在chapter14/tree_api_service/config.py文件中。在这个文件中有许多配置选项,它们大多与 APA102(在第八章中讨论)和舵机(在第十章中讨论)的配置有关。你会发现这个文件和配置选项都有很好的注释。
默认配置足以在树莓派上本地运行示例;但是,你应该检查的一个配置参数是APA102_NUM_LEDS = 60。如果你的 APA102 LED 灯带包含不同数量的 LED,那么请相应地更新此配置。
让我们运行 Tree API 服务程序并创建一些灯光(和移动)!
运行 Tree API 服务
现在是时候运行 Tree API 服务程序并发送 RESTful API 请求使其工作了。以下是运行和测试我们的 Tree API 服务的步骤:
- 切换到
chapter14/tree_api_service文件夹并启动main.py脚本,如下所示:
# Terminal 1
(venv) $ cd tree_api_service
(venv) $ python main.py
* Serving Flask app "main" (lazy loading)
... truncated ...
INFO:werkzeug: * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
- 接下来,打开第二个终端并运行以下
curl命令,将重复的灯光模式序列设置为红色,蓝色,黑色:
# Terminal 2
$ curl -X POST "http://localhost:5000/lights/color?colors=red,blue,black&pattern=yes"
- 同样在终端 2中,运行下一个命令开始让灯光动画起来:
# Terminal 2
$ curl -X POST "http://localhost:5000/lights/animation?mode=left&speed=5"
除了left,你还可以在mode参数中使用其他动画模式,包括right,blink,rainbow和stop。speed参数接受1到10之间的值。
- 要清除或重置 LED 灯带,再次在终端 2中运行以下命令:
# Terminal 2
$ curl -X POST "http://localhost:5000/lights/clear"
- 要使舵机扫动(也就是使树摇晃),在终端 2中运行以下命令:
# Terminal 2
$ curl -X POST "http://localhost:5000/servo/sweep"
舵机应该来回扫动几次。如果你想让舵机扫动更多次或需要增加其运动范围,那么你可以在chapter14/tree_api_service/config.py文件中调整SERVO_SWEEP_COUNT和SERVO_SWEEP_DEGREES配置参数。
如果发现当你移动舵机时 LED 变暗、闪烁或者表现不稳定,或者当你改变 APA102 LED 时舵机抽搐,很可能是你的外部电源无法提供足够的电流来同时运行 LED 和舵机。作为临时措施,如果你没有另一个电源,可以尝试减少 LED 的数量(config.py中的APA102_NUM_LEDS)和/或减少 LED 对比度(config.py中的APA102_DEFAULT_CONTRAST)。这将降低 LED 灯带的电流需求。
- 最后,让我们运行 Web 应用程序,并通过在树莓派桌面上打开 Web 浏览器并导航到 URL
http://localhost:5000来控制我们的 IoTree。您应该看到一个类似于这里图片的网页:
图 14.4 - 示例 IoTree Web 应用程序
尝试以下操作:
-
点击颜色栏中的颜色,观察该颜色被推送到 APA102 LED 灯带。
-
点击Pattern Fill按钮填充 APA102 LED 灯带的选定颜色。
-
点击左侧开始动画。
此 Web 应用程序背后的 JavaScript(在chapter14/tree_api_service/templates/index.html中找到)只是调用我们的 IoTree API,类似于我们已经使用curl做的事情,只是它使用 jQuery 来做。 jQuery 和 JavaScript 超出了本书的范围;但是,在第三章中简要介绍了它们,使用 Flask 进行 RESTful API 和 Web Sockets 的网络。
您将在chapter14/tree_api_service/README.md文件中找到 IoTree 的完整 API 文档集,其中包含curl示例。
我们的 RESTful API 实现提供了本章所需的基本 API 端点;但是,我非常有信心您将能够扩展和调整此示例以适应您自己的项目,或者向您的 IoTree 添加新功能。我将在本章末尾的扩展您的 IoTree 的想法和建议部分提供关于如何根据本书学到的知识扩展您的 IoTree 的建议。
现在我们已经运行并看到如何使用 RESTful API 控制我们的 IoTree 的灯和舵机,接下来我们将看一种替代服务实现,它将允许我们使用 MQTT 控制我们的 IoTree。
配置、运行和使用 Tree MQTT 服务
Tree MQTT 服务程序提供了一个 MQTT 接口,用于通过发布 MQTT 消息到 MQTT 主题来控制树的 APA102 LED 灯带和舵机。您可以在chapter14/tree_mqtt_service文件夹中找到 Tree MQTT 服务程序,并包含以下文件:
-
README.md:控制您的 IoTree 的 MQTT 主题和消息格式的完整列表。 -
main.py:这是程序的主要入口点。 -
config.py:程序配置。 -
apa102.py:这是chapter14/tree_api_service/apa102.py的精确副本。文件 -
servo.py:这是chapter14/tree_api_service/servo.py文件的精确副本。 -
mqtt_listener_client.py:这是一个连接到 MQTT 代理并订阅将接收消息以控制 APA102 和舵机的主题的类。当接收到 MQTT 消息时,它们被转换为 PubSub 消息并使用PyPubSub库发布,我们在第十二章中讨论过,高级 IoT 编程概念-线程、AsyncIO 和事件循环。 -
apa102_controller.py:此代码接收由mqtt_listener_client.py发送的 PubSub 消息,并根据需要更新 APA102 LED 灯带。 -
servo_controller.py:此代码接收由mqtt_listener_client.py发送的 PubSub 消息并控制舵机。
显示了 Tree MQTT 服务程序架构的图表如图 14.5所示:
图 14.5 - Tree MQTT 服务架构块图
以下是 Tree MQTT 服务的高级操作,用虚线表示在前面的图表中发布的 MQTT 发布:
-
red blue消息发布到tree/lights/pattern主题上#1。 -
消息由 Paho-MQTT 客户端在#2 处接收。主题和消息在
mqtt_listener_client.py中的on_message()方法中进行解析,并使用config.py中的MQTT_TO_PUBSUB_TOPIC_MAPPINGS映射字典映射到本地 PubSub 主题pattern。 -
映射的消息和解析的数据使用
PyPubSub库在#3 处分发。 -
apa102_controller.py中的PyPubSub订阅接收pattern主题及其负载数据在#4 处 -
apa102_controller.py处理#5 处的消息和数据,并在 APA102 实例(在apa102.py中定义)上调用适当的方法,直接与重复的红色和蓝色模式的物理 APA102 LED 灯带进行接口和更新。
如果你在想,使用PyPubSub并在mqtt_listener_client.py中重新分发 MQTT 消息是基于我个人偏好的设计决定,目的是将 MQTT 相关的代码和硬件控制相关的代码解耦,以使应用程序更易于阅读和维护。另一种同样有效的方法是在直接响应接收到的 MQTT 消息时在mqtt_listener_client.py中使用apa102.py和servo.py。
现在我们已经了解了我们的 Tree MQTT 服务是如何工作的,在运行我们的 Tree MQTT 服务之前,首先我们需要检查它的配置。我们将在下一步进行。
配置 Tree MQTT 服务
Tree MQTT 服务配置位于chapter14/tree_mqtt_service/config.py文件中。与 Tree API 服务类似,它们主要涉及 APA102 和伺服器的配置。您还会发现这个文件及其配置选项都有很好的注释。
默认配置将足以在树莓派上本地运行示例;但是,就像我们为 Tree API 服务配置所做的那样,请检查并更新APA102_NUM_LEDS = 60参数。
如果您在运行 Tree API 示例时还需要更改APA102_DEFAULT_CONTRAST、SERVO_SWEEP_COUNT或SERVO_SWEEP_DEGREES参数中的任何一个,请现在也更新这些值以供 MQTT 示例使用。
一旦您对配置进行了任何必要的更改,我们将继续运行我们的 Tree MQTT 服务程序并发布 MQTT 消息以使我们的 IoTree 工作。
运行 Tree MQTT 服务程序
现在是时候运行 Tree MQTT 服务程序并发布 MQTT 消息来控制我们的 IoTree 了。以下是运行和测试我们的 Tree MQTT 服务的步骤:
-
我们必须在树莓派上安装并运行 Mosquitto MQTT 代理服务以及 Mosquitto MQTT 客户端工具。如果您需要检查您的安装,请参阅第四章,使用 MQTT、Python 和 Mosquitto MQTT 代理进行网络连接。
-
切换到
chapter14/tree_mqtt_service文件夹并启动main.py脚本,如下所示:
# Terminal 1
(venv) $ cd tree_mqtt_service
(venv) $ python main.py
INFO:root:Connecting to MQTT Broker localhost:1883
INFO:MQTTListener:Connected to MQTT Broker
- 接下来,打开第二个终端并使用以下命令发送 MQTT 消息:
# Terminal 2
$ mosquitto_pub -h "localhost" -t "tree/lights/pattern" -m "red blue black"
LED 灯带将以重复的颜色模式(红色、蓝色、黑色(黑色表示 LED 关闭))点亮。
尝试使用--retain或-r保留消息选项来实验mosquirro_pub。如果您发布了一个保留消息,当它连接到 MQTT 代理并订阅tree/#主题时,它将被重新传递到您的 Tree MQTT 服务,这为您的 IoTree 在重新启动之间恢复其上次状态提供了一种方式。
- 现在,在终端 2中运行以下命令使 LED 灯带动画起来:
# Terminal 2
$ mosquitto_pub -h "localhost" -t "tree/lights/animation" -m "left"
- 要清除或重置 LED 灯带,请在终端 2中再次运行以下命令:
# Terminal 2
$ mosquitto_pub -h "localhost" -t "tree/lights/clear" -m ""
在这个例子(以及步骤 6中的下一个例子)中,我们没有任何消息内容;但是,我们仍然需要传递一个空消息,使用-m ""选项(或者,-n);否则,mosquitto_pub将中止。
- 最后,尝试以下命令来扫描伺服器:
# Terminal 2
$ mosquitto_pub -h "localhost" -t "tree/servo/sweep" -m ""
伺服器将根据chapter14/tree_mqtt_service/config.py中SERVO_SWEEP_COUNT或SERVO_SWEEP_DEGREES设置来来回扫动。
您将在chapter14/tree_mqtt_service/README.md文件中找到 Tree MQTT 服务识别的完整 MQTT 主题和消息格式的完整集合,包括mosquitto_pub示例。
与我们的 RESTful API 示例类似,我们的 MQTT 示例提供了本章所需的最小功能,但提供了一个基本框架,您可以在自己的未来项目中扩展,或者如果您扩展了 IoTree 的功能。
现在我们已经运行并看到如何使用 MQTT 控制 IoTree 的灯和伺服,让我们看看我们可以使用的集成服务,将我们的 Tree MQTT 服务与 dweet.io 耦合。
将 IoTree 与 dweet.io 集成
dweet 集成服务,位于chatper14/dweet_integration_service文件夹中,是一个基于 Python 的集成服务,它接收 dweets 并将它们重新发布为消息到 MQTT 主题。此服务为我们提供了一种简单的方法,将诸如 IFTTT 之类的服务与我们的 Tree MQTT 服务程序集成。
dweet 集成服务由以下文件组成:
-
main.py:主程序入口点。 -
config.py:配置参数。 -
thing_name.txt:保存您的物体名称的地方。当您第一次启动程序时,将创建此文件。 -
dweet_listener.py:核心程序代码。
我们的 dweet 服务的核心部分位于dweet_listener.py文件中。如果您检查此文件,您会注意到它几乎与第二章中涵盖的dweet_led.py文件以及第十三章,IoT 可视化和自动化平台中涵盖的文件几乎相同(除了现在它作为 Python 类包装)。
核心区别在于process_dweet()方法,在下面的代码中显示为(1)行,这里我们不是直接控制 LED,而是拦截 dweet,然后重新发布到 MQTT 主题:
def process_dweet(self, dweet): # (1)
# ...Truncated...
# command is "<action> <data1> <data2> ... <dataN>"
command = dweet['command'].strip()
# ...Truncated...
# elements (List) <action>,<data1>,<data2>,...,<dataN>
elements = command.split(" ")
action = elements[0].lower()
data = " ".join(elements[1:])
self.publish_mqtt(action, data) # (2)
publish_mqtt()方法,在前面的代码中显示为(2)行,在下面的代码中显示为(3)行,然后根据chapter14/dweet_mqtt_service/config.py中的ACTION_TOPIC_MAPPINGS设置,将我们解析的命令字符串转换为基于 MQTT 主题的消息并发布:
def publish_mqtt(self, action, data): # (3)
if action in self.action_topic_mappings:
# Map Action into MQTT Topic
# (Eg mode --> tree/lights/mode).
# See config.py for mappings.
topic = self.action_topic_mappings[action]
retain = topic in self.mqtt_topic_retain_message # (4)
# ... truncated ...
publish.single(topic, data, qos=0, # (5)
client_id=self.mqtt_client_id,
retain=retain, hostname=self.mqtt_host,
port=self.mqtt_port)
# ... truncated ...
请注意,在(5)行,我们使用了 Paho-MQTT 的publish.single()便利方法,而不是我们在第四章中使用的完整的 MQTT 客户端方法,使用 MQTT、Python 和 Mosquitto MQTT 代理进行网络连接(并且在 Tree MQTT 服务程序中也使用)。
目前,我只想指出(4)行,我们在那里设置了retain变量(还注意到它在publish.single()中的使用)。在接下来的部分中,当我们讨论服务配置文件时,我们将更多地讨论此消息保留。
图 14.6 显示了树服务程序架构的图表:
图 14.6 - dweet 集成服务架构块图
这是 dweet 集成服务的高级操作,由前面图中蓝色虚线所示的请求:
-
在#1 处创建了一个 dweet。
-
dweet_listener.py在#2 处接收 dweet 并解析command参数中包含的数据。命令中包含的操作使用config.py中找到的ACTION_TOPIC_MAPPINGS映射字典映射为 MQTT 主题。 -
消息被发布到 MQTT 代理到映射的 MQTT 主题#3。根据
config.py中找到的TOPIC_RETAIN_MESSAGE映射字典,设置消息的保留标志。
发布 MQTT 消息后,如果您的 Tree MQTT 服务正在运行并连接到相同的 MQTT 代理,它将接收 MQTT 消息并相应地更新您的 IoTree。
现在我们了解了我们的 dweet 集成服务的工作原理,然后我们可以运行我们的 dweet 集成服务之前,首先需要检查其配置。我们接下来会做这个。
配置 Tree MQTT 服务
dweet 集成服务的配置位于chapter14/dweet_integration_service/config.py文件中。有许多与服务工作方式相关的配置选项,默认配置将足以在您的 Raspberry Pi 上本地运行此服务,同时您还在那里运行您的 Mosquitto MQTT 代理。这个文件中的配置参数有很好的注释;但是,我将提到ACTION_TOPIC_MAPPINGS和TOPIC_RETAIN_MESSAGE参数:
ACTION_TOPIC_MAPPINGS = {
"clear": "tree/lights/clear",
"push": "tree/lights/push",
... truncated ...
}
dweet 集成服务将dweeted 命令映射到MQTT 主题。决定如何将命令映射到 MQTT 主题的是ACTION_TOPIC_MAPPINGS配置参数。我们将在下一节讨论这个命令的概念。
由 dweet 集成服务映射和使用的 MQTT 主题必须与 Tree MQTT 服务使用的主题相匹配。每个服务的默认配置都使用相同的主题。
以下代码中显示的TOPIC_RETAIN_MESSAGE配置确定了哪些 MQTT 主题将设置其消息的保留标志。正如我们在上一节中指出的那样,这个配置(True或False)用于在single.publish()上设置retained参数:
TOPIC_RETAIN_MESSAGE = {
"tree/lights/clear": False,
"tree/lights/animation": True,
... truncated ...
}
现在我们已经讨论了配置文件,让我们启动我们的 dweet 集成服务,并发送 dweets 来控制我们的 IoTree。
运行 dweet 集成服务程序
我们的 dweet 集成服务通过按照我们在上一节中讨论的配置参数将预定义格式的 dweets 转换为 MQTT 主题和消息来工作。当我们运行和测试 dweet 集成服务时,我们将很快讨论这个 dweet 格式。以下是我们需要遵循的步骤:
-
首先,请确保您在终端上运行了上一节中的Tree MQTT 服务程序。正是 Tree MQTT 服务将接收并处理 dweet 集成服务发布的 MQTT 消息。
-
接下来,在新的终端中导航到
chapter14/dweet_integration_service文件夹,并启动main.py程序,如下所示(请记住您的物体名称将不同):
(venv) $ cd dweet_service
(venv) $ python main.py
INFO:DweetListener:Created new thing name ab5f2504
INFO:DweetListener:Dweet Listener initialized. Publish command dweets to 'https://dweet.io/dweet/for/ab5f2504?command=...'
- 将以下 URL 复制并粘贴到 Web 浏览器中以控制您的 IoTree。使用您输出中显示的物体名称替换
<thing_name>文本:
-
- dweet.io/dweet/for/<thing_name>?command=pattern%20red%20blue%20black
-
dweet.io/dweet/for/<thing_name>?command=animation%20left
-
dweet.io/dweet/for/<thing_name>?command=speed%2010
-
dweet.io/dweet/for/<thing_name>?command=clear
-
dweet.io/dweet/for/<thing_name>?command=sweep
在调用这些 URL 之间可能需要一些时间,然后它将被您的 dweet 集成服务接收。
正如您在前面的 URL 中的command参数中所看到的,我们的 dweet 的格式是<action> <data1> <data2> <dataN>。
在config.py文件中,您将找到默认配置中识别的完整的 dweet 命令字符串集,包括示例 URL,在chapter14/dweet_integration_service/README.md文件中。
干得好!我们刚刚使用 dweet.io 和 MQTT 创建了一个简单的集成服务,并学会了一种简单且非侵入式的方法,可以让我们在互联网上控制我们的 tree,而无需进行任何网络或防火墙配置。
在设计物联网项目并考虑数据在互联网和网络中的传输方式时,通常会发现您需要设计和构建某种集成形式,以桥接建立在不同传输机制上的系统。本节中的示例说明了一个场景,我们在其中将 MQTT 服务(我们的 IoTree MQTT 服务)与基于轮询的 RESTful API 服务(dweet.io)进行了桥接。虽然每个集成都有自己的要求,但希望这个示例为您提供了一个大致的路线图和方法,以便在将来遇到这些情况时进行调整和构建。
现在我们的 dweet 集成服务正在运行,并且已经测试过它正在工作,让我们看看如何将其与 IFTTT 平台一起使用。
通过电子邮件和 Google 助手与 IFTTT 集成
现在是真正有趣的部分——让我们使我们的树可以通过互联网进行控制。作为一个剧透,我不会在这个集成中手把手地指导您,因为在第十三章中详细解释了使用 dweet.io 和 IFTTT 的核心概念,IoT 可视化和自动化平台。特别是,我们学会了如何将我们的树莓派与 IFTTT 和电子邮件集成,以控制 LED。
然而,我将给您我的 IFTTT 配置的截图,以便您可以验证您设置的内容。另外,作为一个额外的奖励,我还将给您一个关于如何与 Google 助手集成的提示和截图,以便您可以语音控制您的 IoTree!
在撰写本文时,IFTTT 有一个 Google 助手服务,可以接受任意口头输入(在 IFTTT 术语中称为成分)。我确实尝试了 Alexa 集成,但不幸的是,Alexa IFTTT 服务无法接受任意输入,因此与我们的示例不兼容。
首先,我们将看一下如何将我们的 IoTree 与电子邮件集成。
与电子邮件的集成
与电子邮件或 Twitter 集成的过程与我们在第十三章IoT 可视化和自动化平台中介绍的内容相同,只是有以下更改:
-
不要使用
LED作为标签(在 IFTTT 的完成触发器字段页面步骤中),使用TREE。这样,您的电子邮件主题可以是#TREE 模式红蓝或#TREE 动画闪烁之类的内容。 -
在配置 That webhook 服务时,您需要使用之前在终端上运行 dweet 集成服务时打印的 dweet URL。我的配置示例如下图所示。请记住,您 URL 中的thing name将是不同的:
图 14.7 – Webhook 配置
- 完成设置 IFTTT Applet 后,尝试发送电子邮件至
trigger@applet.ifttt.com,主题如下:
-
#TREE 模式红蓝黑色
-
#TREE 动画向左
发送电子邮件或推文#TREE 模式红蓝黑色命令后的几分钟内,您的树灯将以这些颜色进行重复变换。同样,发送电子邮件或推文#TREE 动画向左后的几分钟内,您的树灯将开始动画。
请记住,为了使此示例工作,您需要在终端上同时运行 Tree MQTT 服务和 dweet 集成服务。在发送电子邮件或发布推文后,您的 IoTree 更改可能需要一些时间。
一旦您能够通过电子邮件控制您的 IoTree,接下来我们将看一下添加使用 Google 助手进行语音控制所需的步骤。
与 Google 助手的集成
让我们使用 Google 助手使我们的 IoTree 可以通过语音控制。
Google 助手有许多其他形式,包括 Google Home、Google Nest 和 Google Mini。只要它们登录到与 IFTTT 使用的相同的 Google 帐户,这些产品也将与 IFTTT Google 助手集成和您的 IoTree 一起使用。
要创建我们的集成,我们需要将您的 Google 帐户与 IFTTT Google 助手服务链接,并在接收命令时调用 dweet.io URL。以下是要遵循的高级步骤:
-
登录到您的 IFTTT 帐户。
-
创建一个新的 Applet。
-
对于 Applet 的这一部分,请使用 Google 助手服务。
-
接下来,您将被要求连接并允许 IFTTT 使用您的 Google 帐户。按照屏幕上的说明连接 IFTTT 和您的 Google 帐户。
-
现在是选择 Google 助手触发器的时候了。选择“说一个带有文本成分的短语”。示例触发器配置如图 14.8所示:
图 14.8 - Google 助手触发器示例
在前面的屏幕截图中显示的 Tree 中的 中的 中的符号被转换为我们将与我们的 webhook 服务一起使用的 IFTTT 成分(我们将在后面的步骤中看到)。
有了这个触发器配置,你可以说出像以下这样的命令来控制你的 IoTree:
-
- “Tree pattern red blue black”
-
“Set tree animation blink”
-
“Tree clear”
-
现在是配置 IFTTT Applet 的那部分的时间。搜索并选择 WebHook。
-
webhook 服务的配置与我们在步骤 2中之前介绍的与电子邮件集成标题下的过程相同,并且如*图 14.7.*所示。
-
继续并完成创建你的 IFTTT Applet。
-
询问你的 Google 助手以下命令:
-
- “Tree pattern red blue black”
-
“Tree animation blink”
-
“Tree clear”
-
“Tree sweep”(或“tree jingle”)
-
或者
chapter14/dweet_integration_service/README.md文件中记录的任何其他命令
记住,Google 助手承认你的请求后,你的 IoTree 可能需要一会儿才能开始改变。
这是我在 iPhone 上的 Google 助手对话框的屏幕截图:
图 14.9 - 用于控制 IoTree 的 Google 助手对话框
如果集成工作正常,Google 助手将回复“好的,正在更新树”(或者你在步骤 5中使用的任何文本),然后几分钟后,你的 IoTree 将做出响应。
重要的是要记住,我们必须准确地说出命令,就像它们被 dweet 集成服务解释的那样 - 例如,它们会出现在 dweet URL 的命令参数中,如https://dweet.io/dweet/for/<thing_name>?command=pattern red blue black。
记得在它们之前加上“Tree”(或“Set Tree”)这个词。这个文本是触发你的 IFTTT Applet 的。只说一个命令本身不会触发你的 Applet。
如果你使用安卓手机或 iOS 的 Google 助手应用程序,你将能够看到你说出的话是如何转换为文本命令的,这可以帮助你排除不起作用或被误解的命令。
你刚刚学会了如何创建三个 IFTTT 集成,以使用电子邮件和语音控制你的 IoTree,并且你可以轻松地将相同的基本思想和流程适应于控制和自动化本书中所见的其他电子电路。
此外,正如我们在第十三章中讨论的那样,物联网可视化和自动化平台,IFTTT 提供了许多触发器和操作,你可以组合起来构建自动化工作流Applets。在本章和上一章之间,你现在已经创建了几个 Applets,所以我完全相信你将能够探索 IFTTT 生态系统,并创建各种有趣的 Applets,这些 Applets 可以与你的树莓派一起工作。
在我们结束本章(和本书!)之前,我想给你留下一些想法和实验,以进一步扩展你的 IoTree 的功能。
扩展你的 IoTree 的想法和建议
我们在本章中使用的代码和电子设备为我们提供了一个基础,我们可以在此基础上构建。这可能是扩展你的 IoTree,也可能是其他物联网项目的基础。
以下是一些建议,你可以尝试:
-
添加并集成一个 PIR 传感器,每当有人走过你的 IoTree 时,它就会播放一个 RTTTL 曲调。毕竟,如果不一遍又一遍地播放曲调,什么电子圣诞小工具才算完整呢…
-
将 RGB LED 添加并集成到树的顶部(也许在透明的星星内),或者在 APA102 LED 条的位置使用 RGB LED 或与之一起使用。
-
构建多个 IoTree。如果你使用 MQTT,它们将同步!
-
尝试构建 WebSocket 集成和相应的 Web 应用程序。
-
当前的 dweet Google 助手集成要求您精确发出命令。您能否创建一个更模糊的升级-也就是说,可以解析口头文本并找出口头命令是什么?
-
在我们的 IFTTT 示例中,我们使用了 dweet.io(与 MQTT 一起),因此我们不必担心防火墙配置。您可能希望调查在您的位置打开防火墙端口或调查诸如 LocalTunnels(https://localtunnel.github.io/www)或 ngrok(https://ngrok.com)之类的服务。这些方法将允许您使用 IFTTT Webhooks 直接与您的 IoTree 的 RESTful API 进行通信。但是,请记住,我们的 RESTful API 示例没有得到保护-它们没有使用 HTTPS,也没有身份验证机制,例如用户名和密码来限制对 API 的访问,因此您可能还希望研究如何保护基于 Flask 的 API 并首先执行这些升级。
显然,这些只是我的一些建议。在我们的旅程中,我们涵盖了许多电路,所以发挥你的想象力,看看你能想出什么-并且要玩得开心!
总结
恭喜!我们现在已经到达了本章和整本书的结尾!
在本章中,我们运行了电子设备并测试了控制这些电子设备的程序,这些程序构成了物联网圣诞树的基础。我们看到了一个可以控制我们 IoTree 的灯和伺服的 RESTful API,以及一个类似的 MQTT 实现。我们还研究了一个 dweet.io 到 MQTT 的集成服务,我们将其与 IFTTT 配对,以提供一种使用电子邮件和 Google 助手来控制我们 IoTree 的机制。
在本书中的旅程中,我们涵盖了许多概念和技术,包括各种网络技术,电子和接口基础知识,以及使用传感器和执行器与树莓派的一系列实际示例。我们还研究了自动化和可视化平台,并在本章中完成了一个将我们的学习结合在一起的示例。
当我写这本书时,我有一些核心意图。我的一个意图是分享和解释我们如何将传感器和执行器连接到树莓派的原因,以及为什么我们要使用额外的组件,如电阻器来创建电压分压器。我的第二个核心意图是为您提供适用于物联网项目的各种网络技术和选项。
我相信,我们在旅程中学到的软件和硬件基础知识,以及实际示例,将为您提供许多技能和见解,不仅可以帮助您设计和构建自己的复杂物联网项目,还可以在软件、网络和电子方面在基本水平上理解现有的物联网项目是如何工作的。
我真诚地希望你喜欢这本书,学到了很多,并且在阅读过程中获得了许多实用的技巧!祝你在物联网之旅中一切顺利,希望你能创造一些了不起的东西!
问题
最后,这里是一些问题供您测试对本章材料的了解。您将在附录的评估部分中找到答案:
-
在我们的 MQTT 服务示例中,为什么我们使用
PyPubSub重新分发 MQTT 消息? -
在与或调试 IFTTT Google 助手 Applet 集成时,为什么在手机(或平板电脑)上使用 Google 助手应用程序很有用?
-
您正在处理一个现有的天气监测项目,该项目使用 MQTT 作为其网络传输层,以连接许多分布式设备。有人要求您将应用程序与 IFTTT 服务集成。您该如何做?
-
您想要构建多个 IoTree 并使它们一起协同工作。您可以采取哪两种方法来实现这一目标?
-
为什么在本章中我们使用了免费的dweet.io服务?您会在商业物联网项目中使用这种方法吗?
-
我们想要从命令行测试一个 RESTful API 服务。我们可以使用什么命令行工具?
-
您可以使用 MQTT 的哪个特性来在树莓派上电或重新启动时自动初始化 IoTrees?
-
关于问题 7,在设置和部署 Mosquitto MQTT 代理以实现这一目标时,您需要考虑哪些因素?
2万+



被折叠的 条评论
为什么被折叠?



