Skip to content

Latest commit

 

History

History
1017 lines (643 loc) · 60.6 KB

File metadata and controls

1017 lines (643 loc) · 60.6 KB

五、MQTT,Python 和 Mosquitto MQTT 代理网络

在前一章中,我们使用 RESTfulAPI 和 web 套接字方法创建了两个 Python 服务器和附带的网页。在本章中,我们将介绍物联网世界中常见的另一种网络拓扑,称为MQTT消息队列遥测传输*。*

我们将首先设置您的开发环境,并在您的 Raspberry Pi 上安装 Mosquitto MQTT 代理服务。然后,我们将使用 MOSQUITO 附带的命令行工具了解 MQTT 特性,以帮助您单独理解核心概念。之后,我们将继续使用一个 Python IoT 应用程序,该应用程序将 MQTT 用于其消息传递层,是的,它将全部用于控制 LED!

本章将介绍以下主题:

  • 安装 MOSQUITO MQTT 代理
  • 通过示例学习 MQTT
  • 介绍 Python Paho MQTT 客户机库
  • 用 Python 和 MQTT 控制 LED
  • 构建基于 web 的 MQTT 客户机

技术要求

要执行本章中的练习,您需要以下内容:

  • 树莓皮 4 B 型
  • Raspbian OS Buster(带桌面和推荐软件)
  • Python 3.5 版的最低版本

这些需求是本书中代码示例的基础。只要您的 Python 版本是 3.5 或更高版本,就可以合理地期望代码示例在 Raspberry Pi 3 Model B 或不同版本的 Raspbian OS 上无需修改即可工作。

您可以在 GitHub 存储库中的chapter04文件夹中找到本章的源代码,网址如下:https://github.com/PacktPublishing/Practical-Python-Programming-for-IoT

您需要在终端中执行以下命令,以设置虚拟环境并安装本章代码所需的 Python 库:

$ cd chapter04              # 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安装的:

我们将使用在第 2 章Python 和 IoT 入门图 2.7中创建的实验板电路。

安装 MOSQUITO MQTT 代理

MQTT*、*或消息队列遥测传输是专门针对物联网应用的轻量级、简单的消息传递协议。虽然 Raspberry Pi 功能强大,足以利用更复杂的消息传递协议,但如果您将其作为分布式物联网解决方案的一部分使用,您很可能会遇到 MQTT;因此,学习它是非常重要的。此外,它的简单性和开放性使它易于学习和使用。

我们将使用一个名为Mosquitto的流行开源 MQTT b roker 对 MQTT 进行介绍,我们将在您的 Raspberry Pi 上安装它。

The examples we cover in this chapter were performed with the Mosquitto broker and client version 1.5.7, which are MQTT protocol version 3.1.1-complaint. A different version of the broker or client tools will be suitable as long as they are MQTT protocol version 3.1.x-compatible.

要安装 MOSQUITO MQTT 代理服务和客户端工具,请执行以下步骤:

  1. 打开新的终端窗口,执行以下apt-get命令。必须使用sudo执行此操作:
$ sudo apt-get --yes install mosquitto mosquitto-clients
... truncated ...
  1. 要确保 MOSQUITO MQTT 代理服务已启动,请在终端中运行以下命令:
$ sudo systemctl start mosquitto
  1. 检查 MOSQUITO 服务是否已使用以下service命令启动。我们希望看到打印到终端的active (running)文本:
$ systemctl status mosquitto
... truncated ...
 Active: active (running)
... truncated ...
  1. 我们可以使用mosquitto -h命令检查 MOSQUITO 和 MQTT 协议版本。在这里,我们看到 Mosquitto 代理正在使用 MQTT 版本 3.1.1:
$ mosquitto -h
mosquitto version 1.5.7
mosquitto is an MQTT v3.1.1 broker.
... truncated ...
  1. 接下来,我们将配置 MOSQUITO,以便它能够服务于 web 页面并处理 web 套接字请求。在本章后面构建网页客户端时,我们将使用这些功能。

chapter4文件夹中,有一个名为mosquitto_pyiot.conf的文件,在这里部分复制。此文件中有一行需要检查:

# File: chapter04/mosquitto_pyiot.conf
... truncated...
http_dir /home/pi/pyiot/chapter04/mosquitto_www

对于本章中的练习,您需要更新最后一行的http_dir设置,使其成为 Raspberry Pi 上chapter04/mosquitto_www文件夹的绝对路径。如果您在第 1 章中克隆 GitHub 存储库、设置开发环境时使用了建议的文件夹/home/pi/pyiot,则前面列出的路径是正确的。

  1. 接下来,我们使用以下cp命令将mosquitto_pyiot.conf中的配置复制到适当的文件夹中,以便 MOSQUITO 可以加载它:
$ sudo cp mosquitto_pyiot.conf /etc/mosquitto/conf.d/
  1. 现在,我们重新启动 MOSQUITO 服务以加载配置:
$ sudo systemctl restart mosquitto 
  1. 要检查配置是否有效,请访问 Raspberry Pi 上 web 浏览器中的http://localhost:8083URL,您应该会看到类似于以下屏幕截图的页面:

Figure 4.1 – Web page served by the Mosquitto MQTT broker

这是我们在本章后面将要做的事情的赠品!目前,虽然您可以移动滑块,但它不会改变 LED 的亮度,因为我们没有运行 Python 端代码。我们将在本章后面的适当时候讨论这一点。

如果在启动 MOSQUITO MQTT 代理时遇到问题,请尝试以下操作:

  • 在终端中执行sudo mosquitto -v -c /etc/mosquitto/mosquitto.conf。这将在前台启动 MOSQUITO,任何启动或配置错误都将显示在您的终端上。
  • 阅读mosquitto_pyiot.conf文件中的故障排除注释以获取更多建议。

The default configuration of Mosquitto after installation creates an unencrypted and unauthenticated MQTT broker service. The Mosquitto documentation contains details regarding its configuration and how to enable authentication and encryption. You will find links in the Further reading section at the end of this chapter.

现在我们已经安装并运行了 Mosquitto,我们可以探索 MQTT 概念并执行示例以在实践中看到它们。

通过示例学习 MQTT

MQTT 是一种基于代理的发布订阅消息传递协议(通常被解释为发布/订阅),而 MQTT代理(就像我们在上一节中安装的 Mosquitto MQTT 代理一样)是实现 MQTT 协议的服务器。通过使用基于 MQTT 的体系结构,您的应用程序基本上可以将所有复杂的消息处理和路由逻辑移交给代理,以便它们能够保持以解决方案为中心。

MQTT 客户机(例如,您的 Python 程序和我们即将使用的命令行工具)使用代理创建订阅,并订阅他们感兴趣的消息主题。客户端向主题发布消息,然后由代理负责所有消息路由和传递保证。任何客户端都可以扮演订阅者、发布者或两者的角色。

一个涉及泵、水箱和控制器应用的简单概念性 MQTT 系统如图 4.2 所示:

Figure 4.2 – MQTT example

以下是系统组件的高级描述:

  • 水位传感器 MQTT 客户端视为连接到水箱中水位传感器的软件。在我们的 MQTT 示例中,该客户机承担发布者的角色。它定期向 MQTT 代理发送(即,*发布)*关于水箱有多满的消息。
  • 泵 MQTT 客户端想象为一个能够打开或关闭水泵的软件驱动程序。在我们的示例中,此客户端同时承担发布者订户的角色:
    • 作为订户可以接收一条消息(通过订户指令其打开或关闭泵。
    • 作为发布者,它可以发送一条消息,指示泵是否开启,泵水是否关闭。
  • 控制器 MQTT 客户机视为所有控制逻辑所在的应用程序。此客户端还承担发布者订户的角色
    • 作为发布者,该客户端可以发送一条消息,告知泵打开或关闭。
    • 作为订户,该客户端可以同时接收来自水箱液位传感器和泵的消息。

举例来说,控制器 MQTT 客户端应用程序可以配置为当水箱中的水位降至 50%以下时打开泵,当水位达到 100%时关闭泵。该控制器应用程序还可能包括一个仪表板用户界面,该界面显示水箱中的当前水位,以及一个指示泵是开启还是关闭的状态灯。

关于我们的 MQTT 系统,需要注意的一点是,每个客户机都不知道其他客户机—客户机只会连接到 MQTT 代理并与之交互,然后 MQTT 代理会根据需要将消息路由到客户机。此路由是通过使用消息主题实现的,我们将在后面标题为探索 MQTT 主题和通配符的章节中介绍。

可以理解为什么泵需要接收一条消息来告诉它打开或关闭,但是泵需要同时发送一条消息来说明它是打开还是关闭呢?如果你想知道这一点,以下是原因。MQTT 消息是 send 和 forget,这意味着客户机不会获得对其发布的消息的应用程序级响应。因此,在我们的示例中,虽然控制器客户端可以发布一条消息,要求泵打开,但在泵未发布其状态的情况下,控制器无法知道泵是否实际打开。

实际上,泵在每次打开或关闭时都会发布其打开/关闭状态。这将允许控制器的仪表板及时更新泵的状态指示器。此外,泵还将定期发布其状态(就像水位传感器一样),而不受其收到的任何打开或关闭请求的影响。这样,控制器应用程序可以监控泵的连接和可用性,并检测泵是否脱机。

现在,如果您能够掌握前面示例中介绍的基本思想,那么您就可以在更深层次上理解核心 MQTT 概念,这将是本章剩余部分的重点。到我们完成时,您将对如何使用和设计基于 MQTT 的应用程序有一个基本的端到端理解。

我们将从学习如何发布和订阅消息开始。

发布和订阅 MQTT 消息

让我们完成使用 MQTT 发送(即发布)和接收(即订阅)消息的步骤:

  1. 在终端中,运行以下命令。mosquitto_sub(Mosquitto subscribe)是订阅消息的命令行工具:
# Terminal #1 (Subscriber)
$ mosquitto_sub -v -h localhost -t 'pyiot'

方案如下:

In this chapter, we will require two and sometimes three Terminal sessions for the examples. The first line of a code block will indicate which Terminal you need to run a command in; for example, Terminal #1 in the preceding code block, and Terminal #2 in the following code block.

  1. 打开第二个端子并运行以下命令。mosquitto_pub(MOSQUITO publish)是发布消息的命令行工具:

**```py

Terminal #2 (Publisher)

$ mosquitto_pub -h localhost -t 'pyiot' -m 'hello!'


让我们看一下选项:

3.  在**终端**上,我们看到主题和消息`hello!`,打印:

```py
# Terminal #1 (Subscriber)
$ mosquitto_sub -v -h localhost -t 'pyiot'
pyiot hello!

最后一行的格式为<主题><消息负载>。

The hello! message is preceded by the topic name, pyiot, because we have used the -v option to mosquitto_sub. Without the -v option, if we were subscribing to multiple topics, we could not identify which topic a message belonged to.

现在,我们学习了如何通过一个简单的主题发布和订阅消息。但是,我们有没有办法更好地组织这些信息?继续读下去。

探索 MQTT 主题和通配符

MQTT主题用于以分层格式对消息进行分类或分组。我们已经在继续的命令行示例中处理了一些主题,但采用的是非层次化的方式。另一方面,Wildcards订户用来创建灵活主题匹配模式的特殊字符。

下面是一些来自一个假设的带有传感器的建筑物的分层主题示例。层次结构由/字符分隔:

  • level1/lounge/temperature/sensor1
  • level1/lounge/temperature/sensor2
  • level1/lounge/lighting/sensor1
  • level2/bedroom1/temperature/sensor1
  • level2/bedroom1/lighting/sensor1

不需要在 MQTT 代理上预创建主题。使用默认代理配置(我们是),您可以随意发布和订阅主题。

When the Mosquitto broker is configured to use authentication, there is the possibility to restrict access to topics based on a client ID and/or username and password.

消息必须发布到特定的主题,如pyiot,而可以使用通配符+#订阅特定主题或一系列主题:

  • +用于匹配层次结构的单个元素。
  • #用于匹配层次结构中的所有剩余元素(只能在主题查询的末尾)。

对主题和通配符的订阅最好通过示例进行解释。使用前面提到的带有传感器的建筑,考虑下表中的例子:

|

我们想订阅。。。

|

通配符主题

|

主题匹配

| | --- | --- | --- | | 到处都是温度传感器 | +/+/**temperature**/+ |

  • level1/lounge/**temperature**/sensor1

  • level1/lounge/**temperature**/sensor2

  • level2/bedroom1/**temperature**/sensor1

| | 到处都是传感器 | +/+/**lighting**/+ |

  • level1/lounge/**lighting**/sensor1

  • level2/bedroom1/**lighting**/sensor1

| | 2 级上的每个传感器 | **level2**/+/+/+ |

  • **level2**/bedroom1/temperature/sensor1

  • **level2**/bedroom1/lighting/sensor1

| | 2 级上的每个传感器(一种更简单的方法,#匹配所有剩余的孩子) | **level2**/# |

  • **level2**/bedroom1/temperature/sensor1

  • **level2**/bedroom1/lighting/sensor1

| | 到处都只有传感器 1 | +/+/+/**sensor1** |

  • level1/lounge/temperature/**sensor1**

  • level2/bedroom1/lighting/**sensor1**

| | 到处都只有传感器 1(一种更简单的方法,#匹配所有剩余的孩子) | #/**sensor1** | 无效,因为#只能位于主题查询的末尾 | | 每一个话题 | # | 匹配一切 | | 经纪人信息 | $SYS/# | 这是一个特殊的保留主题,代理在其中发布信息和运行时统计信息。 |

Table 1 - MQTT wildcard topic examples

从前面的示例中可以明显看出,在为应用程序设计主题层次结构时需要小心,以便使用通配符订阅多个主题是一致的、合乎逻辑的和容易的。

If you are subscribing using the + or # wildcards with mosquitto_sub , remember to use the -v (--verbose) option so that the topic name is printed in the output, for example, mosquitto_sub -h localhost -v -t '#'.

您可以在命令行上尝试几个示例,通过混合和匹配前面的主题和通配符来了解主题和通配符是如何工作的。以下是一个示例的步骤,mosquitto_sub订阅所有子主题,这些子主题的父温度比根主题低两级:

  1. 在终端中,启动订阅通配符主题的订阅服务器:
# Terminal #1 (Subscriber)
mosquitto_sub -h localhost -v -t '+/+/temperature/+'
  1. 使用*表 1–MQTT 通配符主题示例中的主题,*下面是两个mosquitto_pub命令,它们将发布终端中的mosquitto_sub命令将接收到的消息:
# Terminal #2 (Publisher)
$ mosquitto_pub -h localhost -t 'level1/lounge/temperature/sensor1' -m '20'
$ mosquitto_pub -h localhost -t 'level2/bedroom1/temperature/sensor1' -m '22'

我们刚刚看到了如何使用通配符+*订阅主题层次结构。将主题和通配符结合使用是一项设计决策,您需要在每个项目级别上根据数据的流动方式以及您对客户端应用程序发布和订阅数据的设想做出决策。在设计一致但灵活的基于通配符的主题层次结构上投入的时间将大大有助于您构建更简单且可重用的客户机代码和应用程序。

接下来,我们将了解有关消息服务质量的所有信息,以及这如何影响通过 MQTT 代理发送的消息。

将服务质量应用于消息

MQTT 为单独消息传递提供了三个服务质量QoS)级别—我强调的是单独消息传递,因为 QoS 级别适用于单独消息的传递,而不是主题。通过示例,这将变得更加清晰。

作为开发人员,您为消息规定了 QoS,而代理负责确保消息交付符合 QoS。以下是您可以应用于邮件的 QoS 以及它们对传递的意义:

| QoS 等级 | 意思是 | 发送的消息数 | | 0 级 | 邮件最多只能发送一次,但可能根本不会。 | 0 或 1 | | 一级 | 信息将至少传递一次,但可能会更多。 | 一个或多个 | | 二级 | 该消息将只发送一次。 | 1. |

Table 2 – Message QoS levels

你可能会问这样一个问题:级别 0 和级别 1 似乎有点随机,那么为什么不总是使用级别 2 呢?答案是资源。让我们看看为什么。。。

代理和客户端将消耗更多的资源来处理较高级别的 QoS 消息,而不是较低级别的 QoS 消息。例如,代理将需要更多的时间和内存来存储和处理消息,而代理和客户端通过确认和连接握手消耗更多的时间和网络带宽。

对于许多用例,包括本章后面的示例,我们不会注意到 QoS 级别 1 和 2 之间的差异,也无法实际演示它们(出于一个很好的原因,省略了级别 0,稍后我们将在讨论消息保留和持久连接时看到这一点)。然而,请将注意力放在分布式物联网系统上,每分钟有数千个传感器发布数千条或更多消息,现在围绕 QoS 的设计开始变得更有意义。

QoS 级别适用于消息订阅和消息发布,这在您第一次仔细考虑时可能会显得有些奇怪。例如,一个客户端可以将 QoS 为 1 的消息发布到一个主题,而另一个客户端可以订阅 QoS 为 2 的主题(我知道我说的 QoS 与消息相关,而不是主题,但这里是通过 QoS 相关的主题的流的消息)。此消息是什么 QoS,1 还是 2?对于订户来说,是 1-让我们看看原因。

订阅客户端选择了它想要接收的最高QoS 的消息,但它可能会变得更低。因此,有效地说,这意味着客户端接收的交付 QoS 被降级为发布或订阅的最低 QoS。

这里有几个例子供您思考:

|

发布者发送消息

|

订户在

|

订户得到什么

| | --- | --- | --- | | 服务质量 2 | QoS 0 | 遵循 QoS 0 的消息传递(订户获得消息 0 次或 1 次) | | 服务质量 2 | 服务质量 2 | 遵循 QoS 2 的消息传递(订户只获得一次消息) | | QoS 0 | QoS 1 | 遵循 QoS 0 的消息传递(订户获得消息 0 次或 1 次) | | QoS 1 | 服务质量 2 | 遵循 QoS 1 的消息传递(订户获得消息 1 次或多次) | | 服务质量 2 | QoS 1 | 遵循 QoS 1 的消息传递(订户获得消息 1 次或多次) |

Table 3 – Publisher and subscriber QoS examples

从这些示例中可以看出,在实际中,在设计或集成物联网解决方案时,您需要了解主题两侧的发布者和订阅者所使用的 QoS。QoS 不能单独在任何一侧进行解释。

以下是播放 QoS 场景并实时查看客户端代理交互的步骤:

  1. 在终端中,运行以下命令启动订户:
# Terminal 1 (Subscriber)
$ mosquitto_sub -d -v -q 2 -h localhost -t 'pyiot'
  1. 在第二个终端中,运行以下命令以发布消息:
# Terminal 2 (Publisher)
$ mosquitto_pub -d -q 1 -h localhost -t 'pyiot' -m 'hello!'

在这里,我们再次在终端上订阅,并在终端上发布。以下是与mosquitto_submosquitto_pub一起使用的新选项:

在启用调试(-d的情况下,尝试更改任意一侧的-q参数(为 0、1 或 2)并发布新消息。

  1. 观察终端终端中记录的消息。

终端 1终端 2中出现的调试消息中,您将看到发生在订阅端的 QoS 降级(查找q0q1q2,而在双方,根据客户端和代理执行握手和交换确认时指定的 QoS,您还将注意到不同的调试消息:

# Terminal 1 (Subscriber)
$ mosquitto_sub -d -v -q 2 -h localhost -t 'pyiot' # (1)
Client mosqsub|25112-rpi4 sending CONNECT
Client mosqsub|25112-rpi4 received CONNACK (0)
Client mosqsub|25112-rpi4 sending SUBSCRIBE (Mid: 1, Topic: pyiot, QoS: 2) # (2)
Client mosqsub|25112-rpi4 received SUBACK
Subscribed (mid: 1): 2
Client mosqsub|25112-rpi4 received PUBLISH (d0, q1, r0, m1, 'pyiot', ... (6 bytes)) # (3)
Client mosqsub|25112-rpi4 sending PUBACK (Mid: 1)
pyiot hello!

以下是用户在终端上的调试输出。请注意以下事项:

QoS 是需要掌握的更复杂的 MQTT 概念之一。如果您想深入了解 QoS 级别以及发布者、订阅者和代理之间发生的较低级别的通信,可以在进一步阅读部分找到链接。

现在我们已经介绍了消息 QoS 级别,接下来我们将了解两个 MQTT 特性,它们确保脱机客户端在返回联机时可以接收过去的消息。我们还将看到 QoS 级别如何影响这些功能。

保留邮件以便以后传递

可以指示 MQTT 代理保留发布到主题的消息。消息保留有两种类型,称为保留消息持久连接

  • 保留消息是代理保留在主题上发布的最后一条消息。这也通常被称为最后一条已知的好消息,任何订阅主题的客户机都会自动获取此消息。
  • 持久连接也是关于在不同的上下文中保留消息。如果客户端告诉代理它需要一个持久连接,那么代理会在脱机时为该客户端保留 QoS 1 和 QoS 2 消息。

Unless configured specifically, Mosquitto does not retain messages or connections across server restarts. To persist this information across a restart, a Mosquitto configuration file must contain the entry persistence true. A default installation of Mosquitto on a Raspberry Pi should include this entry, however, to be sure it has also been included in mosquitto_pyiot.conf that we installed earlier. Please consult the official Mosquitto documentation for more information and configuration parameters regarding persistence. You will find a link in the Further reading section at the end of the chapter.

接下来,我们将了解保留消息,并在后续部分介绍持久连接。

发布保留的邮件

发布者可以要求代理保留一条消息作为主题的最后一条已知的好消息。任何新连接的订户都将立即收到最后保留的消息。

让我们通过一个示例演示保留的消息:

  1. 运行以下命令,注意我们从本例中的发布者Terminal#2开始:
# Terminal 2 (Publisher)
$ mosquitto_pub -r -q 2 -h localhost -t 'pyiot' -m 'hello, I have been retained!'

添加了一个新选项-r(--retain),告知代理应该为主题保留此消息。

Only a single retained message can exist for a topic. If you publish another message using the -r option, the previous retained message will be replaced.

  1. 在另一个终端中启动订户,您将立即收到保留的消息:
# Terminal 1 (Subscriber)
$ mosquitto_sub -v -q 2 -h localhost -t 'pyiot'
pyiot hello, I have been retained!
  1. 终端中按Ctrl+C终止mosquitto_sub
  2. 使用步骤 2中的相同命令再次启动mosquitto_sub,您将在终端中看到再次接收到保留消息。

You can still publish normal messages (that is, not using the -r option), however, it's the last retained message indicated by the use of the -r option that newly connecting subscribers will receive.

  1. 最后一个命令显示如何清除以前保留的消息:
# Terminal 2 (Publisher)
$ mosquitto_pub -r -q 2 -h localhost -t 'pyiot' -m ''

在这里,我们发布(带有-r的)带有-m ''的空消息。请注意,我们可以使用-n代替-m ''来表示空消息。保留空消息的效果是实际清除保留的消息。

When you send an empty message to a topic to remove a retained message, any clients currently subscribed to the topic (including offline clients with durable connections — see the next section) will receive the empty message, so your application code must test for and handle empty messages appropriately.

现在,您已经了解并知道如何使用保留的消息,我们现在可以探索 MQTT 提供的另一种消息保留类型,称为持久连接

创建持久的连接

订阅主题的客户端可以要求代理在其脱机时为其保留或排队消息。这在 MQTT 术语中称为持久连接。为了实现持久连接和交付工作,需要以特定方式配置和订阅订阅客户端,如下所示:

  • 客户端必须**在连接时向代理提供唯一的客户端 ID。
  • 客户端必须使用 QoS 1 或 2 订阅(级别 1 和 2 保证交付,但级别 0 不保证)。
  • 客户端只保证获得 QoS 为 1 或 2 的发布的消息。

最后两点涉及一个示例,其中了解主题发布和订阅方面的 QoS 对于 IoT 应用程序设计非常重要。

MQTT brokers can—and the default configuration of Mosquitto on the Raspberry Pi does—retain messages for durable connections between broker restarts.

让我们通过一个示例进行演示:

  1. 启动一个用户,然后立即用Ctrl+C终止该用户,使其离线:
# Terminal #1 (Subscriber)
$ mosquitto_sub -q 1 -h localhost -t 'pyiot' -c -i myClientId123
$ # MAKE SURE YOU PRESS CONTROL+C TO TERMINATE mosquitto_sub

使用的新选项如下:

它的措辞有点落后,但通过使用-c选项启动订户,我们已经要求代理通过不清除连接时存储的任何消息来为我们的客户创建持久连接

If you subscribe to a range of topics using wildcards (for example, pyiot/#) and request a durable connection, then all messages for all topics in the wildcard hierarchy will be retained for your client.

  1. 发布几条消息(当终端的用户仍处于离线状态时):
# Terminal #2 (Publisher)
$ mosquitto_pub -q 2 -h localhost -t 'pyiot' -m 'hello 1'
$ mosquitto_pub -q 2 -h localhost -t 'pyiot' -m 'hello 2'
$ mosquitto_pub -q 2 -h localhost -t 'pyiot' -m 'hello 3
  1. 终端中的用户重新上线,我们将看到步骤 2中发布的消息被传递:
# Terminal 1 (Subscriber)
$ mosquitto_sub -v -q 1 -h localhost -t 'pyiot' -c -i myClientId123
pyiot hello 1
pyiot hello 2
pyiot hello 3

再次尝试步骤 13,只是这次省略了步骤 13中订户的-c选项,您会注意到没有保留任何消息。此外,当有保留消息等待传递时,如果您连接而不使用标志,则所有保留消息都将被清除(如果您想清除客户端的保留消息,这也是您清除保留消息的方式)。

If you are using both retained messages (that is, last known good message) and durable connections together on a single topic and reconnect an offline subscriber, you will *receive the retained message twice—*one is the retained message, while the second is from the durable connection .

在围绕 MQTT 构建解决方案时,您对保留消息和持久连接的了解将是设计具有弹性和可靠性的系统的关键,特别是在需要处理脱机客户端的情况下。保留(最后一个已知的好消息)消息非常适合在客户端重新联机时初始化客户端,而持久连接将帮助您为任何脱机客户端保留并批量传递消息,这些脱机客户端必须能够使用它订阅的主题的每一条消息。

做得好!我们已经介绍了很多内容,您现在实际了解了构建基于 MQTT 的物联网解决方案时将使用的大多数核心 MQTT 功能。我们要了解的最后一个功能是遗嘱

以遗嘱告别

我们用于探索的最终 MQTT 功能称为遗嘱。客户端(发布者或订户)可以向代理注册一个特殊的Will消息,这样,如果客户端突然死亡并与代理断开连接(例如,它失去网络连接或电池耗尽),代理将代表客户端发送Will通知订户设备终止的消息。

遗嘱只是一个信息和主题的组合,类似于我们之前使用的内容。

让我们看看遗嘱在起作用,为此,我们需要三个终端:

  1. 使用以下命令打开终端并启动订户:
# Terminal #1 (Subscriber with Will)
$ mosquitto_sub -h localhost -t 'pyiot' --will-topic 'pyiot' --will-payload 'Good Bye' --will-qos 2 --will-retain

新方案如下:

  1. 使用以下命令在第二个终端中启动订户:
# Terminal #2 (Subscriber listening to Will topic).
$ mosquitto_sub -h localhost -t 'pyiot'
  1. 以及在第三终端中,使用以下命令发布消息:
# Terminal #3 (Publisher)
$ mosquitto_pub -h localhost -t 'pyiot' -m 'hello'
  1. 一旦您在终端上执行步骤 3中的mosquitto_pub命令,您应该会看到终端终端终端中的订户都打印了hello
  2. 终端中,按Ctrl+C终止向经纪人登记遗嘱的认购人。Ctrl+C被视为与经纪人的非优雅或突然断开。
  3. 终端中,我们将看到遗嘱的Good Bye信息:
# Terminal #2 (Subscriber listening to Will topic).
$ mosquitto_sub -h localhost -t 'pyiot'
'Good Bye'

好的,如果订阅者正确地关闭了与代理的连接,那么优雅的断开连接怎么样?我们可以使用带有mosquitto_sub-C选项来演示这一点。

  1. 使用以下命令重新启动终端中的用户:
# Terminal #1 (Subscriber with Will)
$ mosquitto_sub -h localhost -t 'pyiot' --will-topic 'pyiot' --will-payload 'Good Bye, Again' --will-qos 2 --will-retain -C 2

新的-C <count>选项告诉mosquitto_sub断开(正常)连接,并在收到指定数量的消息后退出。

您将立即注意到打印的Good Bye消息。这是因为我们之前在终端中指定了--retain-will选项。此选项使该消息成为该主题的保留消息或最后一条已知的好消息,因此新连接的客户端将接收此消息。

  1. 终端中发布新消息,则终端中的用户退出。注意在终端中没有接收到遗嘱信息Good Bye, Again。这是因为我们的终端订户因为-C选项而优雅地断开了与代理的连接,如果您想知道-C 2中的2,保留的消息将被视为第一条消息。

做得好!如果您已经学习了前面的每个 MQTT 示例,那么您已经介绍了 MQTT 和 Mosquitto 代理的核心概念和使用。请记住,所有这些原则都将适用于任何 MQTT 代理或客户机,因为 MQTT 是一个开放标准。

到目前为止,我们已经了解了消息订阅和发布、如何使用主题隔离消息,以及如何利用 QoS、消息保留、持久连接和遗嘱等功能来控制消息的管理和传递方式。仅此知识就为您提供了使用 MQTT 构建复杂且具有弹性的分布式物联网系统的基础。

我将给您最后一个提示(当我开始使用 MQTT 时,有几次我发现了这一点)。

If your live, retained, or queued durable connection messages seem to be vanishing into a black hole, then check the QoS levels on both your subscribing and publishing clients. To monitor all messages, start a command-line subscriber with QoS 2, listening to the # topic, with both verbose and debug options enabled, for example, mosquitto_sub -q 2 -v -d -h localhost -t '#'.

现在,我们已经完成了 MQTT by example 部分中的所有示例,并学习了如何从命令行与 MQTT 代理交互。接下来,我想简单提及公共经纪人服务。接下来,我们将深入了解代码,并了解如何将 MQTT 与 Python 结合使用。

使用 MQTT 代理服务

如果不想托管自己的 MQTT 代理,可以使用 internet 上的多个 MQTT 代理服务提供程序创建基于 MQTT 的消息传递应用程序。许多公司还提供免费的公共 MQTT 代理,您可以使用它们进行测试和概念的快速验证,但请记住它们是免费的和公共的,所以不要发布任何敏感信息!

如果您在使用免费公共代理服务时遇到挫折、断开连接或意外行为,请使用本地代理测试和验证您的应用程序。您无法可靠地了解或验证开放公共代理的流量拥塞、主题使用情况或配置详细信息,以及这些信息对应用程序的影响。

这里有一些免费的公共经纪人,你可以试试。只需将前面示例中的-h**localhost选项替换为代理的地址即可。有关更多信息和说明,请访问以下页面:

** https://test.mosquitto.org

在下面的部分中,我们将向上移动一个级别。最后,我们讨论了 MQTT 的 Python 部分!请放心,当您开发使用 MQTT 的 IoT 应用程序时,我们刚才介绍的一切都将是非常宝贵的,因为我们介绍的命令行工具和示例将成为您的 MQTT 开发和调试工具包的重要组成部分。我们将应用我们已经学到的核心 MQTT 概念,只是这次使用 Python 和 Paho MQTT 客户机库。

介绍 Python Paho MQTT 客户机库

在进入 Python 代码之前,我们首先需要一个用于 Python 的 MQTT 客户机库。在本章的技术要求部分开始时,我们安装了 Paho MQTT 客户端库,它是requirements.txt的一部分。

If you are new to MQTT and have not read the preceding section, Learning MQTT by example, I recommend stopping now and reading it first so you gain an understanding of MQTT concepts and terminology that will be used in the Python examples that follow.

PAHO MQTT 客户端库来自 Eclipse 基金会,它还维护 MyQuto MQTT 代理。在进一步阅读部分中,您将找到指向官方泛美卫生组织 MQTT 客户机库 API文档的链接。完成本章后,如果您希望加深对本库及其功能的理解,我建议您阅读官方文档和其中的示例。

Python Paho MQTT 库有三个核心模块:

  • 客户端:这为您提供了 Python 应用程序中 MQTT 的完整生命周期管理。
  • 发布者:消息发布的辅助模块。
  • 订户:这是一个用于消息订阅的助手模块。

如果您正在创建更复杂和长期运行的 IoT 应用程序,则客户端模块非常理想,而发布者和订阅者助手模块则适用于短期应用程序和不保证全生命周期管理的情况。

下面的 Python 示例将连接到您的本地 Mosquitto MQTT 代理,我们在前面的安装 Mosquitto MQTT 代理部分中安装了该代理。

我们将使用 Paho 客户机模块,以便创建更完整的 MQTT 示例。然而,一旦您能够理解和理解客户机模块,使用 helper 模块创建替代方案将是小菜一碟。

As a reminder, we will be working with the breadboard circuit we created in Chapter 2, Getting Started with Python and IoT, Figure 2.7.

现在我们已经基本熟悉了 Paho MQTT 库,接下来我们将简要回顾 Python 程序和附带的 web 页面客户机所做的工作,并查看 Paho MQTT 的实际操作。

用 Python 和 MQTT 控制 LED

之前,在安装 Mosquitto MQTT 代理部分,我们通过访问http://localhost:8083URL 测试了安装,该 URL 为我们提供了一个带有滑块的网页。然而,当时,我们无法改变 LED 的亮度。移动滑块时,网页将 MQTT 消息发布到 MOSQUITO 代理,但没有程序接收更改 LED 亮度的消息。

在本节中,我们将看到订阅名为led的主题并处理滑块生成的消息的 Python 代码。我们将从运行 Python 代码开始,确保可以更改 LED 的亮度。

运行 LED MQTT 示例

您将在chapter04/mqtt_led.py文件中找到代码。请在继续之前查看此文件,以全面了解其中包含的内容,然后按照以下步骤操作:

  1. 使用以下命令在终端中运行程序:
# Terminal #1
(venv) $ python mqtt_led.py
INFO:main:Listening for messages on topic 'led'. Press Control + C to exit.
INFO:main:Connected to MQTT Broker
  1. 现在,打开第二个终端窗口并尝试以下操作,LED 应该打开(小心确保 JSON 字符串格式正确):
# Terminal #2
$ mosquitto_pub -q 2 -h localhost -t 'led' -r -m '{"level": "100"}'
  1. 您是否注意到步骤 2中使用的-r--retain选项)?终止并重启mqtt_led.py并观察端子和 LED 中的日志输出。您应该注意到启动时,mqtt_led.py从主题的保留消息接收 LED 的亮度值,并相应地初始化 LED 的亮度。
  2. 接下来,访问http://localhost:8083URL,确保 LED 在移动滑块时改变亮度。

Leave the web page open, and try the command in step 2 again. Observe what happens to the slider — it will stay in sync with the new level value you specified.

  1. 接下来,让我们看看持久连接的作用。再次终止mqtt_led.py并执行以下操作:
    • 在网页上,随机移动滑块约 5 秒钟。当您移动滑块时,消息将发布到代理的led主题中。当mqtt_led.py重新连接时,它们将排队等待发送到mqtt_led.py
    • 重新启动mqtt_led.py并观察端子和 LED。您会注意到终端上出现大量消息,当mqtt_led.py发送和处理排队的消息时,LED 将闪烁。

By default, Mosquitto is configured to queue 100 messages per client that are using a durable connection. A client is identified by its client ID that you provide when connecting to the broker.

现在,我们已经在行动中看到和互动了,让我们看看它的代码。

理解代码

在讨论chapter04/mqtt_led.py中的代码时,请特别注意代码如何连接到 MQTT 代理并管理连接生命周期。此外,在我们介绍代码如何接收和处理消息时,请尝试将代码工作流与我们在上一小节中用来发布消息的命令行示例相关联,运行 LED MQTT 示例

一旦您了解了我们的 Python 代码以及它如何与我们的 MQTT 代理集成,您将拥有一个围绕 MQTT 消息构建的端到端工作参考解决方案,您可以根据自己的需要和项目进行调整。

我们将从进口开始。像往常一样,我们将跳过前面章节中已经介绍过的任何常见代码,包括日志设置和与GPIOZero相关的代码。

进口

在本例中,我们唯一的新导入是针对 Paho MQTT 客户机的:

    import     paho.mqtt.client     as     mqtt      # (1)    

在第(1)行,我们正在导入 Paho MQTTclient类并为其提供别名mqtt。如前所述,这是允许我们在 Python 中创建完整生命周期 MQTT 客户机的客户机类。

接下来,我们将考虑全局变量。

全局变量

第(2)行的BROKER_HOSTBROKER_POST变量指的是本地安装的 MOSQUITO MQTT 代理。端口1883是标准默认 MQTT 端口:

    # Global Variables
        ...        
        BROKER_HOST = "localhost"   # (2)
    BROKER_PORT =     1883
    CLIENT_ID =     "LEDClient"             # (3)
    TOPIC =     "led"                       # (4)
    client =     None         # MQTT client instance. See init_mqtt()   # (5)
    ...

在第(3)行中,我们定义了CLIENT_ID,它将是我们使用 MOSQUITO MQTT 代理来标识程序的唯一客户机标识符。我们必须向代理提供唯一的 ID,以便我们可以使用持久连接

在第(4)行,我们定义了我们的程序将订阅的 MQTT 主题,而在第(5)行,client变量是一个占位符,将分配给 Paho MQTT 客户机实例,我们将很快看到。

设置水平(数据)方法

第(6)行的set_led_level(data)是我们与 GPIOZero 集成以改变 LED 亮度的地方,其方法类似于Chapter3使用 Flask与 RESTful API 和 Web 套接字联网,因此,我们不再在这里讨论内部构件:

def set_led_level(data):  # (6)
   ...

数据参数应该是一个 Python 字典,形式为{ "level": 50 },其中整数在 0 到 100 之间,表示亮度百分比。

接下来,我们将介绍 MQTT 的回调函数。我们将从回顾on_connect()on_disconnect()开始。

on_connect()和 on_disconnect()MQTT 回调方法

on_connect()on_disconnect()回调处理程序是使用 Pahoclient类提供的完整生命周期的示例。我们将看到如何实例化 Pahoclient实例,并在稍后介绍init_mqtt()方法时注册这些回调。

以下代码块中第(7)行的 on_connect() 所关注的参数是client,它是对 Pahoclient类的引用,result_code是描述连接结果的整数。我们看到第(8)行使用了result_code来测试连接是否成功。请注意connack_string()方法,该方法用于连接失败时将result_code转换为人类可读的字符串。

When we speak of the MQTT client and see the client parameter at line (7) in the following code block, remember this is our Python code's client connection to the broker , NOT a reference to a client program such as the web page. This client parameter is very different in meaning to the client parameter we saw used in callback handlers for our Flask-SocketIO Web Socket server in Chapter 3, Networking with RESTful APIs and Web Sockets Using Flask .

作为参考,user_data参数可用于在 Paho 客户端的回调方法之间传递私有数据,而flags是一个 Python 字典,包含来自 MQTT 代理的响应和配置提示:

    def     on_connect(client,     user_data    ,     flags    , result_code): # (7)    

                    if     connection_result_code ==     0    :                    # (8)
                logger.info(    "Connected to MQTT Broker"    )
        else    :
                logger.error(    "Failed to connect to MQTT Broker: "     + 
                     mqtt.connack_string(result_code))

    client.subscribe(TOPIC,     qos    =    2    )                     # (9)

在第(9)行,我们看到了 Pahoclient实例方法subscribe(),用于使用TOPIC全局变量订阅led主题,我们在前面看到了该变量的定义。我们还向代理指出,我们的订阅是 QoS 级别 2。

Always subscribe to topics in an on_connect() handler. This way, if the client ever loses the connection to the broker, it can re-establish subscriptions when it reconnects.

接下来,在下面的第(10)行,我们有on_disconnect()处理程序,其中我们只记录任何断开连接。方法参数的含义与on_connect()处理程序相同:

    def     on_disconnect(    client    ,     user_data    , r    esult_code    ):  # (10)
    logger.error(    "Disconnected from MQTT Broker"    )

现在我们将继续讨论回调方法,该方法处理我们在第(9)行的on_connect()中订阅的led主题的传入消息。

on_message()MQTT 回调方法

每当我们的程序收到订阅主题的新消息时,就会调用第(11)行的on_message()处理程序。该消息可通过msg参数获得,该参数是 MQTTMessage 的一个实例。

在第(12)行,我们访问msgpayload属性并将其解码为字符串。我们希望我们的数据是一个 JSON 字符串(例如,{ "level": 100 },因此我们使用json.loads()将该字符串解析到 Python 字典中,并将结果分配给data。如果消息负载不是有效的 JSON,我们将捕获异常并记录错误:

    def     on_message(    client    ,     userdata    , msg):                        # (11)        
                data =     None        
            try    :                                                      
                    data = json.loads(msg.payload.decode(    "UTF-8"    ))    # (12)
        except     json.JSONDecodeError     as         e    :
        logger.error(    "JSON Decode Error: " 
                       + msg.payload.decode(    "UTF-8"    ))

        if     msg.topic == TOPIC:                                # (13)    
                    set_led_level(data)                               # (14)
        else    :
        logger.error("Unhandled message topic {} 
                 with payload " + str(msg.topic, msg.payload)))

使用第(13)行上msgtopic属性,我们检查它是否与我们预期的led主题匹配,在我们的例子中,它将匹配,因为我们的程序只订阅这个特定主题。但是,这为订阅多个主题的程序在何处以及如何执行条件逻辑和路由提供了一个参考点。

最后,在第(14)行,我们将解析的消息传递给set_led_level()方法,如前所述,该方法改变 LED 的亮度。

接下来,我们将学习如何创建和配置 Paho 客户端。

init_mqtt()方法

我们看到 Paho MQTTclient实例被创建并分配给第(15)行的全局client变量。对这个对象的引用是我们之前在on_connect()on_disconnect()on_message()方法中看到的client参数。

client_id参数设置为我们前面在CLIENT_ID中定义的客户端名称,clean_session=False告诉代理在我们连接时不能清除为我们的客户端存储的任何消息。正如我们在前面的命令行示例中所讨论的,这是一种从后到前的方式,表示我们需要一个持久的连接,以便发布到led主题的任何消息在客户端脱机时都能为其存储:

    def     init_mqtt():
        global     client    
                client = mqtt.Client(                                           # (15)
                        client_id    =CLIENT_ID,
            clean_session    =    False    )

        # Route Paho logging to Python logging.        
                client.enable_logger()                                          # (16)

            # Setup callbacks
                client.on_connect = on_connect                                  # (17)
                client.on_disconnect = on_disconnect
    client.on_message = on_message

        # Connect to Broker.
                client.connect(BROKER_HOST, BROKER_PORT)                        # (18)    

需要注意的一点是第(16)行。我们的程序使用标准的 Python 日志记录包,因此我们需要调用client.enable_logger()以确保获得任何 Paho MQTT 客户机日志消息。错过此呼叫意味着可能无法记录有用的诊断信息。

最后,在第(18)行,我们连接到 Mosquitto MQTT 代理。一旦建立连接,就会调用我们的on_connect()处理程序。

接下来,我们将看到我们的计划是如何开始的。

主要入口点

初始化 LED 和客户端实例后,我们进入程序的主入口点。

我们正在注册一个信号处理器来捕获第(19)行的Ctrl+C组合键。signal_handler方法(未显示)只需关闭我们的 LED 并优雅地断开与代理的连接:

    # Initialise Module
    init_led()
init_mqtt()

    if     __name__ ==     "__main__"    :
    signal.signal(signal.SIGINT, signal_handler)    # (19)    
                logger.info(    "Listening for messages on topic '" 
           + TOPIC +     "'. Press Control + C to exit."    )

    client.loop_start()                                 # (20)
                signal.pause()

在第(20)行,对client.loop_start()的调用允许我们的客户机启动、连接到代理并接收消息。

您是否注意到 LED 程序是无状态的?我们不会在代码或磁盘中存储或持久化任何 LED 级别。我们的程序所做的只是在代理上订阅一个主题,并使用 GPIOZero 更改 LED 的亮度。通过依赖 MQTT 的保留消息(也称为最后一条已知的好消息设施),我们有效地将所有状态管理移交给 MQTT 代理。

我们现在已经完成了对与 LED 和 MQTT 代理交互的 Python 代码的探索。我们学习了如何使用 Python Paho MQTT 库连接到 MQTT 代理并订阅 MQTT 主题。当我们收到关于订阅主题的消息时,我们看到了如何处理它们,并根据消息负载更改了 LED 的亮度级别。

我们介绍的 Python 和 Paho MQTT 框架和示例将为您自己基于 MQTT 的物联网项目提供坚实的起点。

接下来,我们将研究一个使用 MQTT 和 web 套接字的 web 客户机。此 web 客户端将连接到我们的 MOSQUITO MQTT 代理并发布消息以控制我们的 LED。

构建基于 web 的 MQTT 客户机

第 3 章中,使用 Flask与 RESTful API 和 Web 套接字联网,我们介绍了使用 Web 套接字的代码示例,其中包括 HTML 文件和 JavaScript Web 客户端。在本节中,我们还将介绍使用 HTML 和 JavaScript 构建的基于 Web 套接字的 Web 客户端。然而,这一次,我们将利用 MOSQUITO MQTT 代理提供的内置 Web 套接字功能和兼容的 JavaScript Paho JavaScript Web 套接字库(您将在进一步阅读部分找到指向该库的链接)。

For comparison, in Chapter 3, Networking with RESTful APIs and Web Sockets Using Flask, we created our Web Socket server ourselves in Python using Flask-SocketIO, while our web client used the Socket.io JavaScript Web socket library.

我们与我们将要探索的 web 客户端进行了交互,以控制我们的 LED,之前在部分步骤 7中安装了 Mosquitto MQTT 代理。您可能希望快速回顾步骤 7,重新熟悉 web 客户端以及如何在 web 浏览器中访问它。

您将在chapter04/mosquitto_www/index.html文件中找到网页客户端的代码。请在继续之前查看此文件。

理解代码

虽然我们在本例中使用的 JavaScript 库不同,但您会发现 JavsScript 代码的一般结构和用法与我们在第 3 章中看到的基于socket.io的 web 客户端的代码类似,使用 Flask与 RESTful API 和 web 套接字联网 . 像往常一样,我们将从进口开始。

进口

我们的 web 客户端在第(1)行导入 Paho MQTT JavaScript 客户端库:

    <        title        >    MQTT Web Socket Example    </        title        >    
    <        script         src        ="./jquery.min.js"        ></        script        >    
    <        script         src        ="./paho-mqtt.js"        ></        script        >          <!-- (1) -->

paho-mqtt.js也可以在chapter04/mosquitto_www文件夹中找到。

Paho MQTT JavaScript 库的官方文档页面位于https://www.eclipse.org/paho/clients/js ,而其官方 GitHub 页面位于https://github.com/eclipse/paho.mqtt.javascript

When you explore the Paho-MQTT JavaScript API further, start at its GitHub site and make note of any breaking changes that are mentioned. The documentation pages are known to contain code fragments that do not reflect the latest GitHub code base.

接下来,我们将遇到全局变量。

全局变量

在第(2)行,我们初始化一个Client_ID常量,该常量将通过代理识别我们的 JavaScript 客户端。

每个 Paho JavaScript MQTT 客户端在连接到代理时必须具有唯一的主机名、端口、客户端 ID组合。为了确保我们可以在一台计算机上运行多个网页进行测试和演示,我们使用随机数为每个网页创建一个准唯一的客户端 ID:

    <        script         type        ="text/javascript"         charset        ="utf-8"        >    
    messagePubCount = 0;
    const CLIENT_ID = String(Math.floor(Math.random() * 10e16)) // (2)
    const TOPIC   = "led";                                      // (3)

在第(3)行,我们用led定义了TOPIC常量,这是我们不久将订阅和发布的 MQTT 主题的名称。接下来,我们创建客户机实例。

Paho JavaScript MQTT 客户机

在第(4)行,我们创建了 Paho MQTT 客户机实例,并将其分配给client变量。

Paho.MQTT.Client()的参数是代理的主机名和端口。我们通过 MOSQUITO 提供此网页,因此代理的主机和端口将与网页相同:

const client = new Paho.Client(location.hostname,        // (4)
                               Number(location.port),
                               CLIENT_ID); 

您可能已经注意到在http://localhost:8083URL 中,端口是8083,而在 Python 中,我们使用了端口1883

  • 端口1883是代理上的 MQTT 协议端口。我们的 Python 程序直接连接到此端口上的代理。
  • 我们之前在 MOSQUITO 代理上将端口8083配置为 Web 套接字端口。网页可以使用 HTTP 和 Web 套接字协议,而不是 MQTT。

这提出了一个重要的观点。当我们在 JavaScript 代码的上下文中使用术语 MQTT 时,我们实际上是在使用 Web 套接字将 MQTT 思想来回代理给代理。

When we speak of the MQTT client and created the client instance at line (4), remember this is our JavaScript code's client connection to the broker.

接下来,我们将看到如何连接到代理并注册一个onConnect处理程序函数。

连接到代理

我们在第(5)行定义了我们的onConnectionSuccess()处理程序,它将在我们的client成功连接到代理后被调用。成功连接后,我们将更新网页以反映成功连接并启用滑块控件:

onConnectionSuccess = function(data) {         // (5)
    console.log("Connected to MQTT Broker");
    $("#connected").html("Yes");
    $("input[type=range].brightnessLevel")
          .attr("disabled", null);

    client.subscribe(TOPIC);                   // (6)
};

client.connect({                               // (7)
   onSuccess: onConnectionSuccess,
   reconnect: true
 });       

接下来,在第(6)行,我们订阅led主题。我们在第(7)行连接到代理。请注意,我们将onConnectionSuccess函数注册为onSuccess选项。

Remember, similar to the Python example, always subscribe to topics in an onSuccess handler. This way, if the client ever loses the connection to the broker, it can re-establish subscriptions when it reconnects.

我们还指定了reconnect: true选项,以便我们的客户在失去连接时自动重新连接到代理。

It has been observed that it may take up to a minute for the JavaScript Paho-MQTT client to reconnect after losing a connection, so please be patient. This is in contrast to the Python Paho-MQTT client, which reconnects almost instantly.

接下来,我们要回顾另外两个处理程序。

onConnectionLost 和 OnMessageDrive 处理程序方法

在下面的代码中,在第(8)行和第(9)行,我们将看到如何向我们的 Paho MQTTclient实例注册onConnectionLostonMessageArrived处理程序:

client.onConnectionLost = function onConnectionLost(data) {    // (8)
  ...
}

client.onMessageArrived = function onMessageArrived(message) { // (9)
   ...
}

这两个函数在原则上与前面第 3 章中的 socket.io 示例中的相应函数类似,使用 Flask与 RESTful API 和 Web socket 联网,它们根据在各自data中找到的数据更新滑块和网页文本和message参数。

接下来,我们有了文档就绪功能。

JQuery 文档就绪函数

最后,我们在第(1o)行遇到 document ready 函数,在该函数中,我们初始化网页内容并为滑块注册事件侦听器:

$(document).ready(function() {                                   // (10)
    $("#clientId").html(CLIENT_ID);

    // Event listener for Slider value changes.
    $("input[type=range].brightnessLevel").on('input', function() {
        level = $(this).val();

        payload = {
            "level": level
         };

        // Publish LED brightness.
        var message = new Paho.Message(                         // (11)
           JSON.stringify(payload)
        );

        message.destinationName = TOPIC;                        // (12)
        message.qos = 2;
        message.retained = true;                                // (13)
        client.send(message);
    });
});

在第(11)行的 sliders 事件处理程序中,我们看到了如何创建 MQTT 消息。注意JSON.stringify(payload)的用法。Paho.Message构造函数需要一个String参数,而不是Object,因此我们必须将有效负载变量(即Object转换为字符串。

从第(12)行开始,我们将消息发布主题设置为ledmessage.destinationName = TOPIC,然后将其 QoS 级别标记为 2。

接下来,在第(13)行的message.retained = true中,我们表示希望保留此消息,以便自动将其发送给订阅led主题的新客户端。此信息的保留允许mqtt_led.py在重启之间重新初始化 LED 的先前亮度。

做得好!我们现在已经介绍了一个简单的基于 MQTT 的应用程序的 Python 和 JavaScript 方面。

总结

在本章中,我们探讨并实践了 MQTT 的核心概念。在 Raspberry Pi 上安装和配置 Mosquitto MQTT 代理之后,我们直接学习了命令行上的一系列示例。我们学习了如何发布和订阅 MQTT 消息,如何理解主题结构和名称层次结构,以及如何将 QoS 级别附加到消息。

我们还讨论了持久连接和保留消息,这是 MQTT 代理提供的两种机制,用于存储消息以供以后传递。我们通过探索一种称为Will的特殊消息和主题类型来结束 MQTT 概念的演练,在这种情况下,客户机可以向代理注册一条消息,在客户机突然失去连接的情况下,该消息会自动发布到主题。

接下来,我们回顾并浏览了一个 Python 程序,该程序使用 Paho Python MQTT 库订阅 MQTT 主题,并根据收到的消息控制 LED 的亮度。随后,我们浏览了一个使用 Paho JavaScript MQTT 库构建的网页,该库发布了 Python 程序使用的消息。

现在,您已经掌握了 MQTT 的实用知识,并且现在可以为自己的物联网应用程序利用实用的代码框架。这是我们在前面章节中探讨的其他网络方法和代码框架的补充,如 dweet.io 服务、Flask RESTful 和 Flask SocketIO。您在项目中使用的方法完全取决于您试图创建的内容,当然还有您自己的个人偏好。对于大型项目和需要与外部系统集成的项目,您可能会发现自己需要同时利用多种方法,甚至需要研究和探索其他技术。我毫不怀疑,您对我们到目前为止介绍的其他网络方法的学习和理解将是有价值的,并有助于您理解您遇到的其他方法。

在下一章将 Python 连接到物理世界中,我们将探讨一系列与如何将 Raspberry Pi 连接到物理世界相关的主题。除了 GPIOZero 和 PiGPIO 之外,我们还将介绍流行的 Python GPIO 库选项,并介绍 Raspberry Pi 可用的不同类型的电子接口选项和配置。我们还有一个全面的练习,我们将在您的 Raspberry Pi 中添加一个模数转换器,并使用它创建一个程序来探索 PWM 技术和概念。

问题

最后,以下是一系列问题,供您测试有关本章内容的知识。您可以在本书的评估部分找到答案:

  1. 什么是 MQTT?
  2. 您保留的 MQTT 消息永远无法传递。你应该检查什么?
  3. MQTT 代理在什么条件下发布will消息?
  4. 您选择使用 MQTT 作为 IoT 应用程序的消息传递层,并且必须确保发送和接收消息。所需的最低 QoS 级别是多少?
  5. 您使用 MQTT 开发应用程序并使用 Mosquitto 代理,但现在需要使用不同的代理。这对您的代码库和部署配置意味着什么?
  6. 您应该在代码中的什么位置(提示:哪个处理程序方法)订阅 MQTT 主题,为什么?

进一步阅读

本章从操作层面介绍了 MQTT 的基础知识。如果您想从协议和数据级别了解更多关于 MQTT 的信息,HiveMQ(MQTT 代理和服务提供商)在上提供了一个关于 MQTT 协议的优秀的 11 部分系列 https://www.hivemq.com/blog/mqtt-essentials-part-1-introducing-mqtt

MOSQUITO MQTT 代理和客户端工具的主页位于以下 URL:

我们在本章中使用的 Paho MQTT 库的文档和 API 参考可从以下 URL 获得:

除了 MQTT、HTTP RESTful API 和 Web 套接字之外,还有专门为受限设备设计的补充通信协议,称为 CoRA 和 MQTT-NS。Eclipse 基金会对这些损坏的协议进行了总结。https://www.eclipse.org/community/eclipse_newsletter/2014/february/article2.php 。***