Python-物联网指南-全-
Python 物联网指南(全)
原文:
zh.annas-archive.org/md5/729fe5cbaba4b57748ef3646477e357a译者:飞龙
前言
物联网(IoT)正在改变我们的生活方式,代表着 IT 行业最大的挑战之一。开发者正在创建低成本设备,收集大量数据,相互交互,并利用云服务和基于云的存储。全世界的制造商都在进行令人着迷的项目,将日常物体转变为带有传感器和执行器的智能设备。
咖啡杯不再是简单的物体了——它可以向你的智能手表发送消息,表明里面的液体已经达到合适的温度,这样你就可以放心地喝,无需担心是否太热。在你收到消息之前移动咖啡杯,你的可穿戴设备会振动以表明你还不必喝它。
你可以在智能手机上检查咖啡机的咖啡水平,无需担心订购更多的咖啡:当咖啡水平不足以覆盖剩余的一天时,咖啡机会自动在线订购咖啡。你只需在智能手表上批准咖啡机建议的在线订单。基于某些统计算法,咖啡机将知道订购的最佳时间。
当更常见的访客到达办公室时会发生什么?他们的智能手表或智能手机将与咖啡机通信,并在脱咖啡因咖啡的消耗量增加过多的情况下下订单。我们有智能咖啡杯、智能咖啡机、智能手表、智能手机和可穿戴设备。所有这些设备都利用云服务创建一个智能生态系统,能够为我们提供我们一天中所需的所有不同类型的咖啡。
英特尔 Galileo Gen 2 板是一个功能强大且多功能的迷你计算机板,适用于物联网项目。我们可以启动 Linux 版本,并轻松执行与板上不同组件交互的 Python 脚本。本书将指导你如何使用 Python 2.7.3、其库和工具,从选择硬件到所有必要的堆栈开发物联网原型。如果你需要一个更小的板或替代品,书中包含的所有示例都与英特尔 Edison 板兼容,因此,在你需要的情况下,你可以切换到这块板。
Python 是最受欢迎的编程语言之一。它是开源的、多平台的,你可以用它开发任何类型的应用程序,从网站到极其复杂的科学计算应用程序。总有一个 Python 包可以让我们更容易地完成任务,避免重复造轮子并更快地解决问题。Python 是开发完整物联网堆栈的理想选择。本书涵盖了将日常物体转变为物联网项目所需了解的所有内容。
本书将使您能够使用 Python 作为编程语言从头开始原型设计和开发物联网(IoT)解决方案。您将利用您现有的 Python 知识从现实世界捕获数据,与物理对象交互,开发 API,并使用不同的物联网协议。您将使用特定的库轻松地与底层硬件、传感器、执行器、总线和显示器一起工作。您将学习如何利用 Intel Galileo Gen 2 板上的所有 Python 包。您将准备好成为一名创造者,并成为激动人心的物联网世界的一部分。
本书涵盖内容
第一章,理解和设置基础物联网硬件,将我们引入使用 Python 和 Intel Galileo Gen 2 板迈向物联网(IoT)的旅程。我们将了解该板提供的不同功能,并可视化其不同组件。我们将理解不同引脚、LED 和连接器的含义。我们将学习如何检查板的固件版本,并在必要时进行更新。
第二章,在 Intel Galileo Gen 2 上使用 Python,引导我们通过许多程序,使我们能够使用 Python 作为主要编程语言,使用我们的 Intel Galileo Gen 2 板创建物联网项目。我们将编写 Linux Yocto 镜像到 microSD 卡,配置板使其启动此镜像,更新许多库以使用它们的最新版本,并启动 Python 解释器。
第三章,使用 Python 与数字输出交互,教我们如何使用两个不同的库在 Python 中控制数字输出:mraa 和 wiring-x86。我们将把 LED 和电阻连接到面包板上,并编写代码来点亮 0 到 9 个 LED。然后,我们将改进我们的 Python 代码以利用 Python 的面向对象特性,并准备代码以便构建一个 API,该 API 将允许我们通过 REST API 打印 LED 上的数字。
第四章,使用 RESTful API 和脉冲宽度调制,让我们使用 Tornado Web 服务器、Python、HTTPie 命令行 HTTP 客户端以及 mraa 和 wiring-x86 库。我们将生成许多版本的 RESTful API,这将允许我们在连接到局域网的计算机和设备上与板进行交互。我们将能够编写和发送 HTTP 请求,在 LED 上打印数字,改变三个 LED 的亮度级别,以及使用 RGB LED 生成数百万种颜色。
第五章,使用数字输入、轮询和中断,解释了使用轮询读取按钮状态与使用中断和中断处理程序之间的区别。我们将编写代码,使用户可以通过面包板上的按钮或 HTTP 请求执行相同的操作。我们将结合代码,该代码对按钮状态的改变做出反应,与使用 Tornado Web Server 构建的 RESTful API 一起工作。我们将创建类来封装按钮以及与 mraa 和 wiring-x86 库相关的必要配置。
第六章,使用模拟输入和本地存储,解释了如何使用模拟输入来测量电压值。我们将使用模拟引脚和 mraa 以及 wiring-x86 库来测量电压。我们将能够将可变电阻转换为电压源,并使其能够通过模拟输入、光敏电阻和分压器来测量黑暗程度。当环境光线变化时,我们将触发动作,并且我们将处理模拟输入和输出。我们将利用 Python 标准库中的日志功能以及 Intel Galileo Gen 2 板中包含的 USB 2.0 连接器来注册事件。
第七章,使用传感器从现实世界获取数据,让我们使用各种传感器从现实世界获取数据。我们将利用 upm 库中包含的模块和类,这将使我们能够轻松地开始使用模拟和数字传感器。我们将了解考虑测量单位的重要性,因为传感器总是以特定的单位提供测量值,我们必须考虑这一点。我们将测量适当的加速度或 g 力的幅度和方向、环境温度和湿度。
第八章,显示信息和执行操作,教我们如何通过 I²C 总线将不同的显示连接到我们的板上。我们将使用带有 RGB 背光的 LCD 显示器,然后将其替换为 OLED 点阵显示器。我们将编写代码,利用 upm 库中包含的模块和类来处理 LCD 和 OLED 显示器,并在其上显示文本。我们还将编写与模拟伺服机构交互的代码。我们将控制轴以允许我们创建一个仪表图表来显示通过传感器获取的温度值。我们的 Python 代码将使物体移动。
第九章, 与云服务合作,教您如何结合许多基于云的服务,这将使我们能够轻松发布从传感器收集的数据,并在基于网络的仪表板上可视化它。我们将使用 MQTT 协议及其发布/订阅模型来处理板上的命令,并通过消息指示命令是否成功处理。首先,我们将与使用 MQTT 协议的 PubNub 云服务合作。然后,我们将使用 Mosquitto 和 Eclipse Paho 开发相同的示例。我们将能够编写能够与我们的 IoT 设备建立双向通信的应用程序。
第十章, 使用基于云的 IoT 分析分析大量数据,解释了物联网与大数据之间的紧密关系。我们将使用英特尔 IoT 分析,这是一种基于云的服务,允许我们组织由多个 IoT 设备及其传感器收集的大量数据。我们将使用 requests 包编写几行 Python 代码来与英特尔 IoT 分析 REST API 进行交互。我们将了解英特尔 IoT 分析为我们提供分析大量数据的不同选项,并将定义触发警报的规则。
您需要为本书准备的东西
为了使用连接到英特尔 Galileo Gen 2 板和启动 Python 示例所需的不同工具,您需要任何具有英特尔 Core i3 或更高 CPU 和至少 4 GB RAM 的计算机。您可以使用以下任何操作系统:
-
Windows 7 或更高版本(Windows 8、Windows 8.1 或 Windows 10)
-
Mac OS X Mountain Lion 或更高版本
-
任何能够运行 Python 2.7.x 的 Linux 版本
-
任何支持 JavaScript 的现代浏览器。
您还需要一块英特尔 Galileo Gen 2 板和一块带有 830 个连接点(用于连接的孔)和 2 条电源线的面包板。
此外,您还需要不同的电子元件和分线板来构建许多章节中包含的示例。
本书面向的对象
本书非常适合想要探索 Python 生态系统中的工具以构建自己的 IoT 网络栈和 IoT 相关项目的 Python 程序员。来自创意和设计背景的人也会发现本书同样有用。
习惯用法
在本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和推特用户名如下所示:“默认情况下,pip 软件包管理系统,它使得安装和管理用 Python 编写的软件包变得容易,并未安装。”
代码块设置如下:
if __name__ == "__main__":
print ("Mraa library version: {0}".format(mraa.getVersion()))
print ("Mraa detected platform name: {0}".format(mraa.getPlatformName()))
number_in_leds = NumberInLeds()
# Count from 0 to 9
for i in range(0, 10):
number_in_leds.print_number(i)
time.sleep(3)
当我们希望将你的注意力引到代码块的一个特定部分时,相关的行或项目将以粗体显示:
class NumberInLeds:
def __init__(self):
self.leds = []
for i in range(9, 0, -1):
led = Led(i, 10 - i)
self.leds.append(led)
def print_number(self, number):
print("==== Turning on {0} LEDs ====".format(number))
for j in range(0, number):
self.leds[j].turn_on()
for k in range(number, 9):
self.leds[k].turn_off()
新术语和重要词汇以粗体显示。屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:“下次你需要在板上上传文件时,你不需要在站点管理器对话框中设置一个新的站点,以便建立 SFTP 连接。”
注意
警告或重要提示会出现在这样的框中。
小贴士
小贴士和技巧看起来像这样。
读者反馈
我们始终欢迎读者的反馈。请告诉我们你对这本书的看法——你喜欢什么或不喜欢什么。读者的反馈对我们来说很重要,因为它帮助我们开发出你真正能从中获得最大收益的图书。
要发送给我们一般性的反馈,只需发送电子邮件至 <feedback@packtpub.com>,并在邮件的主题中提及书名。
如果你在一个领域有专业知识,并且你对撰写或为图书做出贡献感兴趣,请参阅我们的作者指南,网址为www.packtpub.com/authors。
客户支持
现在你已经是 Packt 图书的骄傲拥有者,我们有许多事情可以帮助你从购买中获得最大收益。
下载示例代码
你可以从你的账户中下载这本书的示例代码文件。www.packtpub.com。如果你在其他地方购买了这本书,你可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给你。
你可以通过以下步骤下载代码文件:
-
使用你的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的支持标签上。
-
点击代码下载与勘误。
-
在搜索框中输入书名。
-
选择你想要下载代码文件的图书。
-
从下拉菜单中选择你购买这本书的地方。
-
点击代码下载。
你也可以通过点击 Packt Publishing 网站上图书网页上的代码文件按钮来下载代码文件。你可以通过在搜索框中输入书名来访问此页面。请注意,你需要登录到你的 Packt 账户。
下载文件后,请确保使用最新版本的软件解压缩或提取文件夹:
-
WinRAR / 7-Zip for Windows
-
Zipeg / iZip / UnRarX for Mac
-
7-Zip / PeaZip for Linux
该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Internet-of-Things-with-Python。我们还有其他来自我们丰富图书和视频目录的代码包可供下载,网址为github.com/PacktPublishing/。请查看它们!
下载本书的颜色图像
我们还为您提供了一个包含本书中使用的截图/图表彩色图像的 PDF 文件。彩色图像将帮助您更好地理解输出的变化。您可以从www.packtpub.com/sites/default/files/downloads/InternetofThingswithPython_ColorImages.pdf下载此文件。
勘误
尽管我们已经尽最大努力确保内容的准确性,错误仍然可能发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以避免其他读者感到沮丧,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分下。
盗版
互联网上版权材料的盗版是一个持续存在的问题,涉及所有媒体。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现任何形式的我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过<copyright@packtpub.com>与我们联系,并附上疑似盗版材料的链接。
我们感谢您的帮助,以保护我们的作者和我们为您提供有价值内容的能力。
问题
如果您对本书的任何方面有问题,您可以通过<questions@packtpub.com>联系我们,我们将尽力解决问题。
第一章. 理解和设置基础物联网硬件
在本章中,我们将开始使用 Python 和英特尔 Galileo Gen 2 主板探索通往物联网(IoT)的旅程。Python 是最受欢迎且功能最全面的编程语言之一。您可以使用 Python 创建多平台桌面和 Web、移动以及科学应用。您可以使用 Python 处理大量数据,并开发在大数据场景中流行的复杂算法。有成千上万的 Python 包,这些包允许您将 Python 的能力扩展到您能想象到的任何领域。
我们可以利用我们对 Python 及其所有包的现有知识来编写我们物联网生态系统的不同部分。我们可以使用 Python 中我们喜爱的面向对象特性,在交互英特尔 Galileo Gen 2 主板及其连接的电子组件的代码中。我们可以使用不同的包,使我们能够轻松运行 Web 服务器并提供 RESTful API。我们可以使用我们已知的所有包来与数据库、Web 服务和不同的 API 交互。Python 使我们能够轻松地进入物联网世界。我们不需要学习另一种编程语言,我们可以使用我们已知的并喜爱的语言。
首先,我们将学习英特尔 Galileo Gen 2 主板包含的功能。我们将:
-
理解英特尔 Galileo Gen 2 主板及其组件
-
识别输入/输出和 Arduino 1.0 引脚排列
-
了解额外的扩展和连接功能
-
理解主板中发现的按钮和 LED
-
检查并升级主板固件
理解英特尔 Galileo Gen 2 主板及其组件
我们希望轻松地将我们的想法变为现实。我们希望能够在拍手时在屏幕上显示生日快乐的信息。我们希望从现实世界中收集大量数据。我们希望创建能够跟踪我们一整天所有活动的可穿戴设备。我们希望使用数据执行操作并与现实世界元素交互。我们希望使用我们的移动设备来控制机器人。我们希望能够根据从温度传感器获取的数据确定天气是热还是冷。我们希望根据从湿度传感器收集的值做出决策。
我们希望测量杯子里有多少我们最喜欢的饮料,并在 LCD 点阵显示屏上显示信息。我们希望分析所有连接到互联网的事物收集的数据。我们希望利用我们现有的 Python 编程技能,成为物联网时代的创造者。
我们将使用 Python 作为主要的编程语言来控制连接到英特尔 Galileo Gen 2 主板的不同组件,特别是 Python 2.7.3。然而,在我们成为创造者之前,了解这块主板的一些功能是必要的。
在我们打开英特尔 Galileo Gen 2 板后,我们将找到以下元素:
-
英特尔 Galileo Gen 2 板
-
一个 12 VDC(直流伏特),1.5 A(安培)的电源
以下图像显示了未开封的英特尔 Galileo Gen 2 板的前视图:

让我们花几分钟时间看看板的前视图。我们会注意到许多熟悉的元素,例如以太网插孔、主机 USB 端口和许多标记的引脚。如果我们之前有 Arduino UNO R3 板的经验,我们会很容易意识到许多元素与该板上的位置相同。如果我们有嵌入式系统和电子方面的经验,我们会很容易意识到该板提供了与支持 I²C 总线的设备通信所需的引脚(SCL 和 SDA)。如果我们没有任何先前的经验,我们将在接下来的章节中包含的示例中学习我们可以用所有这些引脚做什么。
下一个图像显示了 Fritzing 开源免费软件中英特尔 Galileo Gen 2 板的图形表示。正如你可能注意到的,图形表示仅包括板上的重要部件和所有我们可以布线和连接的东西,以及必要的标签以帮助轻松识别。我们将使用 Fritzing 图来展示我们必须完成的全部布线,以便通过本书完成每个示例项目。

小贴士
您可以从fritzing.org/download/下载 Fritzing 的最新版本。Fritzing 在 Windows、Mac OS X 和 Linux 上运行。您将在具有 FZZ 扩展名(*.fzz)的文件中找到本书中包含的所有示例的 Fritzing 草图,这些文件是您可以下载的代码文件的一部分。文件以 Fritzing 0.92 版本保存。因此,您可以在 Fritzing 中打开草图,检查面包板视图,并根据您的需求进行任何更改。
下一个图像显示了英特尔 Galileo Gen 2 板的电子原理图表示,即板的符号表示,以便于理解与板相关的电子电路的互连。电子原理图也称为电路图或电气图。符号包括板上提供的所有引脚,显示为连接器。我们可以轻松识别出板上出现的许多标签,它们是符号中每个连接器的标签。Fritzing 允许我们同时使用面包板和电子原理图表示。

小贴士
当你打开书中包含的每个示例的 Fritzing 文件时,你可以通过点击位于主 Fritzing 窗口顶部的面包板或原理图按钮,轻松地在面包板视图和原理图视图之间切换。
下一个图像展示了英特尔 Galileo Gen 2 板的系统框图。该图是英特尔 Galileo Gen 2 设计文档中包含的内容的一部分:www.intel.com/content/dam/www/public/us/en/documents/guides/galileo-g2-schematic.pdf。

英特尔 Galileo Gen 2 板是 Arduino 认证的嵌入式计算机,我们将用它来开发和原型化我们的物联网项目。该板基于英特尔架构,并使用英特尔 Quark SoC X1000 系统芯片,也称为 SoC 或应用处理器。SoC 是一个单核单线程的应用处理器,与英特尔 Pentium 32 位指令集架构(ISA)兼容。其运行速度高达 400 MHz。以下图像显示了位于板中心大约位置的 SoC。以下页面提供了关于英特尔 Quark SoC X1000 的详细信息:ark.intel.com/products/79084/Intel-Quark-SoC-X1000-16K-Cache-400-MHz

在 CPU 的右侧,该板有两个集成电路,提供 256 MB 的 DDR3RAM(随机存取存储器)内存。操作系统和 Python 将能够与这种 RAM 内存一起工作。就像在任何计算机中发生的那样,RAM 内存在我们关闭板子后会丢失其信息。因此,我们说 RAM 是易失的,因为当内存未供电时,存储在其中的数据会丢失。以下图像显示了 DDR3 内存芯片。

此外,该板还提供了以下板载存储器的访问:
-
512 KB 的嵌入式SRAM(静态随机存取存储器)。
-
8 MB 的 Legacy SPI NOR 闪存,非易失性存储器。其目的是存储板的固件和草图。
-
11 KB 的EEPROM(电可擦可编程只读存储器)。它是非易失性的,我们可以用它来存储我们自己的数据。
识别输入/输出和 Arduino 1.0 引脚布局
该板提供了以下 I/O 引脚:
-
14 个数字 I/O 引脚
-
六个PWM(脉冲宽度调制)输出引脚
-
六个模拟输入引脚
该板在硬件和软件引脚上与为 Arduino Uno R3 设计的 Arduino 保护板兼容。编号从 0 到 13 的 14 个数字 I/O 引脚位于板的右上角,它们还包括相邻的AREF和GND引脚,如 Arduino Uno R3 所示。引脚配置也称为 Arduino 1.0 引脚排布。
小贴士
保护板是我们可以插在 Intel Galileo Gen 2 板上以扩展其功能的板。例如,您可以插入一个提供两个高电流电机控制器的保护板,或者插入一个添加 LED 矩阵的保护板。
如同 Arduino Uno R3 一样,我们可以使用这些数字 I/O 引脚中的六个作为 PWM(脉冲宽度调制)输出引脚。具体来说,带有波浪线符号(**)作为数字前缀的引脚具有这种功能:引脚**11、10**、**9、6**、**5和~3。以下是从左到右组成引脚头的引脚:
-
SCL
-
SDA
-
AREF
-
GND
-
13
-
12
-
~11
-
~10
-
~9
-
8
-
7
-
~6
-
~5
-
4
-
~3
-
2
-
TX->1
-
RX<-0
下一个图像显示了 14 个数字 I/O 引脚和六个带有波浪线符号(~)作为前缀的 PWM 输出引脚。从左数起的前两个引脚是两个 I²C 总线线:SCL(串行时钟)和SDA(串行数据)。从左数起的最后两个引脚,标记为TX->1和RX<-0,是 UART 0 端口引脚。UART端口代表通用异步接收/发送器。

编号从A0到A5的六个类似输入引脚位于板的右下角,如 Arduino Uno R3 所示。在模拟输入引脚的左侧,我们可以看到以下组成电源头的电源引脚:
-
电源
-
IOREF
-
RESET
-
3.3V
-
5V
-
GND
-
GND
-
VIN
电源头中的VIN引脚提供输入电压,该电压通过电源插头供应到板上。盒子中包含的电源提供 12V。然而,该板可以在 7V 到 15V 的输入电压范围内运行。该板还支持以太网供电(PoE),这通过以太网电缆将电力传递到板上,同时传递数据。
以下截图显示了电源引脚,也称为电源头,以及六个模拟输入引脚:

该板包括一个标记为IOREF的跳线,允许我们在 3.3V 或 5V 保护板操作之间进行选择,并为所有 I/O 引脚提供电压级别转换。根据跳线位置,该板可以与 3.3V 或 5VArduino 保护板一起工作。默认情况下,IOREF跳线设置为 5V 位置,因此初始设置允许我们使用 5V 保护板。以下截图显示了IOREF跳线设置为 5V 位置。

小贴士
电源头中的IOREF引脚提供基于IOREF跳线位置的运行电压参考。因此,根据IOREF跳线位置,IOREF引脚的电压参考可以是 5V 或 3.3V。
在板的右侧,有一个 6 针,具体是 2x3 针,ICSP(In-Circuit Serial Programming)头,标记为ICSP。此头的位置也与 Arduino 1.0 引脚排列兼容。以下截图显示了 ICSP 头:

识别额外的扩展和连接能力
电源插孔位于板的左侧,标记为PWR。在电源插孔下方,有一个微 SD 卡连接器,标记为SDIO。微 SD 卡连接器支持最大容量为 32 GB 的微 SD 卡。我们将使用微 SD 卡作为我们的主要存储来存储操作系统、Python 和必要的库。该板可以从微 SD 卡启动。因此,我们可以将微 SD 卡视为我们与物联网项目一起工作的主要硬盘。以下截图显示了连接电源供应的电源插孔以及连接了 8 GB 微 SD 卡的微 SD 卡连接器。

以太网插孔位于板的左上角,标记为10/100 LAN,位于电源插孔上方。以太网端口支持以太网和快速以太网标准,因此它可以与 10 Mbps 或 100 Mbps 的标称吞吐量率一起工作。以太网端口对于将板连接到我们的 LAN 并通过 IP 地址访问它来说非常有用。有一个带有以太网板上网络接口卡 MAC(媒体访问控制)地址的粘性标签。MAC 地址也称为物理地址。
以下截图显示了以太网外壳上的这个粘性标签以及插入其中的电缆。图中所示板的 MAC 地址为 A1B2C3D4E5F6。如果我们使用将 MAC 地址表示为用冒号(:)分隔的六组两个十六进制数字的约定,则 MAC 地址将表示为 A1:B2:C3:D4:E5:F6。MAC 地址对于在我们的 LAN DHCP 客户端列表中识别板来说非常有用。出于安全原因,原始 MAC 地址已被擦除,我们使用一个假 MAC 地址作为示例。

在以太网插孔旁边,有一个六针、3.3V USB TTL UART 头,具体是 UART 1,即板上的第二个 UART 端口。六针、3.3V USB TTL UART 头的右侧有以下标签:
-
CTS
-
TXO
-
RXI
-
无标签(空)
-
RTS
-
GND
在以太网插孔和 UART 引脚旁边,有一个微型 USB Type B 连接,标记为USB 客户端。我们可以使用这个连接将计算机连接到板子,以便执行固件更新或传输草图。
小贴士
然而,重要的是要知道,您不能通过 USB 关闭板子的电源。此外,在连接电源供应到板子之前,切勿将电缆连接到微型 USB Type B 连接。
在微型 USB 连接旁边,有一个 USB 2.0 主机连接器,标记为USB 主机。该连接器支持最多 128 个 USB 端点设备。我们可以使用这个连接器插入 USB 闪存驱动器以进行额外存储,USB 键盘、USB 鼠标或我们可能需要的任何其他 USB 设备。然而,在我们插入任何设备之前,我们必须考虑必要的驱动程序以及它们与我们将在板上使用的 Linux 发行版的兼容性。
以下图片显示了从左到右依次是 UART 引脚、微型 USB 连接器和 USB 2.0 端口,旁边是以太网插孔。

以下图片显示了带有所有连接器和插孔的侧面视图。从左到右,USB 2.0 端口、微型 USB 连接器、UART 引脚和带有绿色(速度)和黄色(链路)LED 的以太网插孔。

板子的背面提供了一个迷你 PCI Express 插槽,也称为 mPCIe 插槽,符合 PCIe 2.0 特性,标记为PCIE。该插槽与全尺寸和半尺寸 mPCIe 模块兼容,我们可以将其连接到板上以扩展其功能。半尺寸 mPCIe 模块需要适配器才能连接到板上的插槽。
小贴士
通过 mPCIe 插槽添加另一个 USB 主机端口是可能的。mPCIe 插槽对于提供 WiFi、蓝牙和其他类型的不作为板载功能的连接非常有用。
在 mPCIe 插槽旁边,有一个 10 针 JTAG(联合测试行动小组)引脚,标记为JTAG。可以使用 JTAG 接口与支持英特尔 Quark SoC X1000 应用处理器的调试软件进行调试,例如免费和开源的片上调试软件 OpenOCD。
下一张图片显示了带有 mPCIe 插槽和 JTAG 引脚的板子背面视图。

理解按钮和 LED
板子的前面提供了两个位于底部的按钮,分别标记为重启和重置。以下图片显示了这两个按钮:

标有REBOOT的按钮重置英特尔 Quark SoC X1000 应用处理器。标有RESET的按钮重置草图以及任何连接到板上的屏蔽。在这本书中,我们不会使用 Arduino 草图,但我们可能需要重置一个屏蔽。
在 USB 2.0 主机连接器旁边有五个矩形 LED:连接器左侧有两个 LED,右侧有三个 LED。以下是对 LED 标签及其含义的说明:
-
OC:当板通过 micro USB 连接器供电时,LED 指示过流。然而,此功能在英特尔 Galileo Gen 2 板上未启用,因此我们只是将 LED 关闭。如果 LED 打开,则表示板工作不正常或电源供应失败。通常,当板被砖化时,此 LED 会打开。我们说板被砖化,当它不再工作,技术上就像一块砖一样有用。
-
USB:这是 micro USB 就绪 LED。在板完成引导过程后,LED 会打开,允许我们将 micro USB 电缆连接到标有USB CLIENT的 micro USB 连接。我们绝对不应该在 LED 打开之前将电缆连接到 micro USB 连接,因为这可能会损坏板。
-
L:该 LED 连接到数字 I/O 引脚的 13 号引脚,因此,向 13 号引脚发送高电平会打开此 LED,而低电平会关闭它。
-
ON:这是一个电源 LED,表示板已连接到电源。
-
SD:该 LED 指示与 microSD 卡连接器(标为SDIO)的 I/O 活动,因此,当板在 microSD 卡上读取或写入时,此 LED 会闪烁。
以下图像显示了 USB 2.0 主机连接器左侧的OC和USBLED 以及右侧的L、ON和SDLED。

该板包括一个集成的实时时钟,称为 RTC。可以将 3V 的纽扣电池连接到 RTC,以在开机周期之间保持 RTC 运行。不幸的是,电池不包括在包装内。两个 RTC 纽扣电池连接器位于英特尔 Quark SoC X1000 应用处理器左下角,标为COIN,并带有电池图标。下一张图像显示了两个 RTC 纽扣电池连接器。

检查和升级板上的固件
有时,板中包含的原始固件是英特尔 Galileo Gen 2 可用的最新固件。然而,在某些情况下,我们可能需要固件更新,因此始终确保我们使用的是板上固件的最新可用版本。
小贴士
固件更新可以解决错误和兼容性问题。因此,始终使用最新的固件是非常方便的。然而,如果您对遵循固件更新程序没有信心,保留随板卡提供的版本会更方便。在更新固件过程中,如果操作不当或发生断电,可能会损坏板卡,即可能将板卡变成砖头。您肯定不希望这种情况发生在您的板卡上。
如果您想检查当前固件版本并确定是否需要升级板卡的固件,您必须遵循以下步骤:
请访问英特尔 Galileo 固件和驱动程序下载页面downloadcenter.intel.com/download/24748/Intel-Galileo-Firmware-and-Drivers-1-0-4。该网址是本书编写时的最新固件版本:1.0.4。然而,请始终确保您从英特尔驱动程序和软件下载中心下载的是最新可用的版本。如果版本高于 1.0.4,则操作步骤相同,只需将 1.0.4 替换为新版本号。
网络浏览器将显示支持的操作系统的可用下载。网页无法检测您正在使用的操作系统,因此它为所有支持的操作系统的下载提供支持:Windows、Mac OS X 和 Linux。以下图片显示了网页的内容:

您将在操作系统独立下找到 PDF 用户指南:IntelGalileoFirmwareUpdaterUserGuide-1.0.4.pdf。点击按钮,阅读并接受英特尔软件许可协议,并阅读英特尔 Galileo 固件更新工具文档。文档包括在 Windows 和 Linux 中安装驱动程序的必要步骤。Mac OS X 不需要安装任何驱动程序。
在您安装驱动程序或开始检查板卡固件版本的流程之前,请从板卡上移除所有连接,例如微型 USB 线缆和任何插入 USB 2.0 主机接口的 USB 设备。移除任何草图以及微型 SD 卡。您的英特尔 Galileo Gen 2 板卡应该像您拆箱时一样为空。
将电源连接到板卡,等待几秒钟,直到标有USB的矩形 LED 灯亮起。一旦此 LED 灯亮起,启动过程就已经完成,此时可以安全地将 USB Type A 到 Micro-B USB 线缆从您的计算机连接到板卡上标有USB CLIENT的微型 USB 接口。不幸的是,该线缆并未包含在板卡的包装盒内。以下图片显示了已完成连接并正在 Mac OS X 上运行固件更新工具的英特尔 Galileo Gen 2 板卡。

如果您正在使用 Windows 或 Linux,请按照IntelGalileoFirmwareUpdaterUserGuide-1.0.4.pdf文档中解释的步骤安装必要的驱动程序。
小贴士
您的板已经连接到计算机,因此您可以跳过文档中的此步骤。实际上,许多版本的文档没有解释您在通过微型 USB 连接器将板连接到计算机之前必须等待 USB LED 灯亮起,这导致许多板出现了意外问题。
一旦您在计算机上安装了驱动程序,并且您的板已连接到它,您就可以下载并执行适用于您的操作系统的英特尔 Galileo 固件更新器的 ZIP 文件。对于 Windows,文件是IntelGalileoFirmwareUpdater-1.0.4-Windows.zip。对于 Mac OS X,文件是IntelGalileoFirmwareUpdater-1.0.4-OSX.zip。通常您需要向下滚动网页以找到适用于您的操作系统的适当文件。一旦点击所需的文件按钮,您必须阅读并接受英特尔软件许可协议,然后才能下载 ZIP 文件。
在 Windows 中,下载IntelGalileoFirmwareUpdater-1.0.4-Windows.zip文件,打开它,并执行 ZIP 文件中包含的firmware-updater-1.0.4.exe应用程序。英特尔 Galileo 固件更新工具窗口将出现,并会自动选择之前安装的驱动程序生成的虚拟 COM 端口号,例如COM3,在端口下拉菜单中。应用程序将与板通信,然后将在更新固件版本中显示工具包含的固件版本,并在当前板固件中显示当前板的固件版本。
以下图像显示了在 Windows 10 上运行的英特尔 Galileo 固件更新工具。在这种情况下,该工具具有最新的固件版本,因为它提供1.0.4版本,当前板的固件是1.0.2。

在 Mac OS X 中,下载IntelGalileoFirmwareUpdater-1.0.4-OSX.zip文件,然后执行下载的固件更新器应用程序。请注意,根据您的安全设置和 OS X 版本,您可能需要授权操作系统运行该应用程序。英特尔 Galileo 固件更新工具窗口将出现,并会自动选择连接到板的生成的 USB 调制解调器设备,例如/dev/cu.usbmodem1411,在端口下拉菜单中。应用程序将与板通信,然后将在更新固件版本中显示工具包含的固件版本,并在当前板固件中显示当前板的固件版本。
以下图像显示了在 OS X El Capitan 上运行的英特尔 Galileo 固件更新工具。在这种情况下,该工具提供了最新的固件版本,因为它提供的是1.0.4版本,而当前板的固件是1.0.2版本,这与 Windows 版本的情况相同。

如果您决定需要并想要更新固件,考虑到之前解释的风险,您只需点击更新固件按钮,等待工具指示过程已完成。无论是 Windows 还是 Mac OS X,该过程都是相同的。
提示
在工具指示固件更新已完成之前,不要从连接到板的计算机上拔下 USB 电缆,不要从板上断开电源供应,不要关闭应用程序。在固件更新过程中,最安全的做法是将电源供应连接到不间断电源(UPS),以保护它免受电源故障的影响。
固件更新过程完成后,工具显示您在板上具有与工具提供的固件版本相同的版本时,您可以关闭应用程序,并从计算机和板上断开 USB 电缆。确保您不要将 USB 电缆留在板上,然后拔掉电源供应。
测试你的知识
-
英特尔 Galileo Gen 2 板包括:
-
板上具有三个天线的 WiFi 连接性。
-
板上具有以太网连接性。
-
板上具有蓝牙连接性。
-
-
英特尔 Galileo Gen 2 板与以下广泛的硬件和引脚兼容:
-
Arduino Uno R3 屏蔽。
-
Arduino Pi 屏蔽。
-
Raspberry Pi 屏蔽。
-
-
标有 IOREF 的跳线允许我们:
-
选择 3.5V 或 7V 的屏蔽操作,并为所有 I/O 引脚提供电平转换。
-
选择 3.3V 或 5V 的屏蔽操作,并为所有 I/O 引脚提供电平转换。
-
重置板。
-
-
标有 L 的 LED 连接到以下数字 I/O 引脚:
-
11
.。 -
12
.。 -
13
.。
-
-
板的背面提供了以下插槽:
-
Mini PCI Express。
-
PCMCIA。
-
雷电
-
摘要
在本章中,我们学习了英特尔 Galileo Gen 2 板提供的不同功能。我们可视化了板上的不同组件,并理解了不同引脚、LED 和连接器的含义。我们还学习了如何检查板的固件版本,并在必要时更新它。
现在我们已经识别了板上的不同组件,我们必须准备它以 Python 作为我们的主要编程语言来工作,这是我们将在下一章中讨论的内容。
第二章。在英特尔 Galileo Gen 2 上使用 Python
在本章中,我们将开始使用 Python 和英特尔 Galileo Gen 2 板探索物联网(IoT)之旅。我们将:
-
设置环境以开始使用 Python 作为主要的编程语言
-
在板启动 Yocto Linux 发行版后检索板的分配 IP 地址
-
连接到板的操作系统并在其上运行命令
-
安装和升级必要的库,以便使用 Python 与板的组件交互
-
在板上运行我们的第一行 Python 代码
设置板以使用 Python 作为编程语言
为了将 Python 作为主要的编程语言来控制这块板,需要做一些工作。我们需要以下额外的元素,这些元素不包括在板的包装盒内:
-
至少 4GB 的 microSD 卡,最大支持容量为 32GB。使用速度等级 4 或更快的 microSD 卡会更方便。请注意,您将丢失 microSD 卡上的所有内容。
-
一个 microSD 到 SD 存储卡适配器。适配器通常包含在 microSD 卡的包装内。
-
一台带有 SD 存储卡读卡器的电脑。大多数现代笔记本电脑和台式电脑都包括 SD 存储卡读卡器。但是,如果您没有,您可以在电脑的空闲 USB 端口上购买一个 USB SD 存储卡读卡器并将其连接。SD 存储卡读卡器实际上是读写设备,因此我们可以通过 microSD 到 SD 存储卡适配器将它们用于写入 microSD 卡。
-
一根以太网线。
-
一个带有空闲以太网端口的以太网交换机或 WiFi 路由器。您将把英特尔 Galileo Gen 2 板连接到您的局域网。
小贴士
如果您无法访问您的局域网交换机,您将需要向您的网络管理员寻求建议。
下一个图片显示了一个标有SDC4/8GB(左侧)的 8GB 速度等级 4microSD 卡和一个 microSD 到 SD 存储卡适配器(右侧)。

我们必须从英特尔物联网开发套件图像存储库网站下载 Yocto Linux 元分布启动映像的最新版本。在您的网络浏览器中打开iotdk.intel.com/images/并下载网页上列出的iot-devkit-latest-mmcblkp0.direct.bz2压缩文件,其中包含启动映像。您也可以通过在您的网络浏览器中输入完整 URL 来下载它:iotdk.intel.com/images/iot-devkit-latest-mmcblkp0.direct.bz2。
小贴士
我们将使用devkit-latest-mmcblkp0.direct.bz2文件,最后修改于 2015 年 7 月 2 日。请确保您不要下载早于这个日期的任何版本,因为之前版本中使用的包名与本章后面提供的说明不兼容。
下载文件后,必须解压缩下载的图像文件,并将提取的图像写入 microSD 卡。在 Windows 和 Mac OS X 中的操作步骤不同。
在 Windows 中,你可以使用 7-Zip 从下载的.bz2文件中提取内容。7-Zip 是一个免费的开源软件,你可以从www.7-zip.org下载。
一旦从.bz2文件中提取了 Yocto Linux 元分布引导映像iot-devkit-latest-mmcblkp0.direct,你必须将此映像写入 microSD 卡。将 microSD 卡插入 microSD 到 SD 存储卡适配器,并将适配器插入计算机的 SD 卡读卡器。
Win32 Disk Imager 工具是 Windows 的一个映像写入器,允许我们将映像写入 U 盘或 SD/CF 卡。你可以使用这款免费软件将映像写入 microSD 卡。你可以从sourceforge.net/projects/win32diskimager/files/Archive下载它。最新版本的安装程序是Win32DiskImager-0.9.5-install.exe文件。一旦安装了软件,请注意,你必须以管理员身份在 Windows 中运行应用程序。你可以右键单击应用程序的图标,并选择以管理员身份运行。
点击图像文件文本框右侧的图标,将文件过滤器从磁盘映像 (*.img .IMG) 更改为,这样你就可以选择具有直接**扩展名的 Yocto Linux 引导映像。
在设备下拉菜单中选择 Windows 分配给 microSD 卡的驱动器字母。
小贴士
确保选择正确的驱动器字母,因为该驱动器的所有内容都将被擦除,并用引导映像覆盖。如果你选择了错误的驱动器字母,你将丢失整个驱动器的内容。
点击写入,然后在确认覆盖对话框中点击是。现在,等待工具完成将内容写入 microSD 卡。以下截图显示了Win32 Disk Imager工具在 Windows 10 中写入映像到 microSD 卡时的进度。

工具将花费几分钟时间将映像写入 microSD 卡。一旦写入过程完成,工具将显示一个包含写入成功消息的完成对话框。点击确定关闭对话框并关闭 Win32 Disk Imager 窗口。
在 Windows 中弹出 microSD 卡,然后从 SD 卡读卡器中移除 SD 存储卡适配器。
在 Mac OS X 和 Linux 中,您可以使用bunzip2从下载的bz2文件中提取内容,使用diskutil卸载 microSD 卡,并使用dd将映像写入 microSD 卡。您还可以打开终端,在下载文件的文件夹中运行以下命令来解压下载的 bz2 文件:
bunzip -c iot-devkit-latest-mmcblkp0.direct
小贴士
您需要非常小心地处理命令,以避免擦除硬盘分区等错误的设备。
还可以通过在 Finder 中双击下载的 bz2 文件来解压该文件。然而,我们将在终端窗口中运行更多命令,因此,使用命令开始解压文件会更简单。
一旦从 bz2 文件中提取了 Yocto Linux 引导映像iot-devkit-latest-mmcblkp0.direct,您就必须将此映像写入 microSD 卡。将 microSD 卡插入 microSD 到 SD 内存卡适配器,然后将适配器插入计算机的 SD 内存卡读卡器。启动磁盘工具应用程序并检查连接到读卡器的媒体详细信息。例如,在任一 MacBook 笔记本电脑上,您可以通过点击APPLE SD 卡读卡器媒体然后点击信息按钮来找到信息。检查设备名称或BSD 设备节点中列出的名称。我们将使用此名称在写入 microSD 卡的引导映像的命令中。以下图片显示了磁盘工具应用程序和设备名称为disk2的 microSD 卡的信息。我们只需要在收集到的设备名称前添加/dev/作为前缀,因此,在这个示例案例中,完整的名称是/dev/disk2。

也可以通过运行diskutil命令列出所有设备并找出分配给 microSD 卡的设备名称来收集信息。然而,此命令提供的信息阅读起来有点困难,而磁盘工具应用程序则使得理解哪个是内存卡读卡器的设备名称变得容易。以下命令列出所有设备:
diskutil list
以下是由此命令生成的示例输出。高亮显示的行显示了 microSD 卡的设备名称:/dev/disk2。
/dev/disk0 (internal, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *121.3 GB disk0
1: EFI EFI 209.7 MB disk0s1
2: Apple_CoreStorage Macintosh HD 120.5 GB disk0s2
3: Apple_Boot Recovery HD 650.0 MB disk0s3
/dev/disk1 (internal, virtual):
#: TYPE NAME SIZE IDENTIFIER
0: Apple_HFS Macintosh HD +120.1 GB disk1
Logical Volume on disk0s2
4BADDDC3-442C-4E75-B8DC-82E38D8909AD
Unencrypted
/dev/disk2 (internal, physical):
#: TYPE NAME SIZE IDENTIFIER
0: FDisk_partition_scheme *7.7 GB disk2
1: Linux 53.5 MB disk2s1
2: Linux 1.4 GB disk2s2
小贴士
确保您记下正确的设备名称,因为驱动器的所有内容都将被擦除并覆盖以写入引导映像。如果您指定了错误的设备名称,您将丢失整个驱动器的内容。
使用以下命令卸载 microSD 卡。如果您收集到的设备名称是disk2,则需要将/dev/devicename替换为/dev/disk2。如果不是,请替换为适当的设备名称。
sudo diskutil unmountDisk /dev/devicename
终端将要求您输入密码,并将卸载 microSD 卡。运行以下dd命令,将名为iot-devkit-latest-mmcblkp0.direct的输入文件中的镜像写入之前步骤中收集到的设备名称的 microSD 卡。如果收集到的设备名称是disk2,则需要将of=/dev/devicename替换为of=/dev/disk2。如果不是,请将其替换为适当的设备名称。该命令没有包含设备名称,这样您就不会意外地覆盖任何磁盘。
sudo dd if=iot-devkit-latest-mmcblkp0.direct of=/dev/devicename bs=8m
然后,将镜像写入 microSD 卡需要一些时间。等待命令完成,并再次显示Terminal提示符。请注意,这通常需要几分钟,并且在写入过程完成之前没有任何进度指示。命令完成后,您将看到以下输出:
169+1 records in
169+1 records out
1417675776 bytes transferred in 1175.097452 secs (1206433 bytes/sec)
现在,使用以下命令卸载 microSD 卡。如果收集到的设备名称是disk2,则需要将/dev/devicename替换为/dev/disk2。如果不是,请将其替换为适当的设备名称。
sudo diskutil unmountDisk /dev/devicename
关闭终端窗口,然后从 SD 卡读卡器中取出 SD 内存卡适配器。
现在,我们有一个包含 Python 2.7.3 和许多有用库和工具的 Yocto Linux 分布的 microSD 卡。是时候让英特尔 Galileo Gen 2 板从写入 microSD 卡的 Yocto 镜像启动了。
确保板子已断电,并将带有 Yocto 镜像的 microSD 卡放入板上的 microSD 卡槽中,该槽标有SDIO。以下图片显示了插入板槽中的 microSD 卡。

然后,使用以太网线将板子连接到您的局域网,并将板子的电源插头插入以打开板子并启动它。您会注意到标有SD的矩形板上 LED 指示灯表示 microSD 卡正在活动中。等待大约 30 秒以确保板子完成启动过程。您会注意到在启动过程完成后,标有SD的 LED 停止闪烁。
获取板子的分配 IP 地址
板子已经使用 Yocto Linux microSD 卡完成了启动过程,并通过以太网端口连接到我们的局域网。DHCP 服务器已为板子分配了一个 IP 地址,我们需要知道它以便在 Yocto Linux 控制台上运行命令。我们有多种方法可以获取板子的分配 IP 地址。我们将探讨不同的选项,您可以根据您的局域网配置选择最方便的一个。
如果开发板连接到无线路由器的一个以太网端口,并且我们可以访问路由器的 Web 界面,我们可以轻松地知道分配给开发板的 IP 地址。一些路由器的 Web 界面会显示有线客户端列表。由于我们的开发板是通过以太网线连接的,它将被列为有线客户端之一,设备的 MAC 地址将与板上的以太网外壳上的粘性标签上的 MAC 地址打印机匹配。以下图片显示了路由器 Web 界面中的有线客户端列表,列表中包括一个名为galileo的设备,其 MAC 地址为A1-B2-C3-D4-E5-F6,这与板上打印不带连字符(-)的 MAC 地址A1B2C3D4E5F6相匹配。分配给开发板的 IP 地址是192.168.1.104。出于安全原因,原始 MAC 地址已被擦除,我们在这个例子中使用了一个假 MAC 地址。

有时,路由器的 Web 界面不提供显示有线客户端列表的选项。如果我们的路由器是这样的,我们总是能够检索提供所有连接到局域网的无线或有线设备分配的 IP 地址的 DHCP 客户端列表。我们只需要找到具有板 MAC 地址的设备。以下图片显示了路由器 Web 界面中的 DHCP 客户端列表,列表中包括一个名为galileo的设备,其 MAC 地址为A1-B2-C3-D4-E5-F6,这与板上打印不带连字符(-)的 MAC 地址A1B2C3D4E5F6相匹配。分配给开发板的 IP 地址是192.168.1.104。

另一个选项是安装 Bonjour 浏览器,通过这种零配置网络实现自动发现板及其在局域网上的服务,而无需知道分配给板的 IP 地址。
在 Windows 中,从hobbyistsoftware.com/bonjourbrowser下载、安装并启动免费的 Windows Bonjour 浏览器。该应用程序将显示许多可用的 Bonjour 服务,其中galileo为它们的名称。以下截图显示了以_ssh._tcp服务类型和galileo名称选中的详细信息。IP 地址部分显示了 SSH 服务的 IP 地址和端口号:192.168.1.105:22。我们可以使用任何 SSH 客户端的 IP 地址来连接到板。此外,Bonjour 浏览器还让我们知道板有一个 SFTP 服务,这将使我们能够轻松地从运行在板上的 Yocto Linux 传输和接收文件。

在 OS X 中,从www.tildesoft.com下载并运行免费的 Bonjour 浏览器。您可以点击重新加载服务来刷新发现的设备和它们的服务。以下图片显示了 Bonjour 浏览器中列出的板子和其服务。您必须点击每个右箭头以展开每个列出的服务的详细信息。在这种情况下,所有服务都由名为galileo的同一设备提供。一旦展开设备,应用程序将显示 IPv4 和 IPv6 地址。SSH (_ssh._tcp.)服务类型列出了一个名为galileo的设备,其 IPv4 地址为192.168.1.105,端口号为22。我们可以使用该 IP 地址和任何 SSH 客户端连接到板子。Bonjour 浏览器还显示了 SFTP 服务的详细信息。

小贴士
SSH 代表安全外壳协议,其默认端口是 22。Yocto Linux 在默认端口运行 SSH 服务器,因此,在 SSH 客户端中无需指定端口,我们只需指定发现的 IP 地址即可。
连接到板子的操作系统
现在,我们需要使用 SSH 客户端连接到板子上运行的 Yocto Linux 并更新一些我们将用于与板子组件和功能交互的库。OS X 和 Linux 都包含终端中的ssh命令。但是,Windows 不包括ssh命令,我们必须安装一个 SSH 客户端。
在 Windows 中,我们可以使用免费的开源 PuTTY SSH 和 telnet 客户端。但是,如果您在 Windows 中更喜欢其他 SSH 客户端,您可以使用任何其他软件。我们在终端中执行的命令将取决于我们使用的 SSH 客户端。
您可以从www.putty.org或www.chiark.greenend.org.uk/~sgtatham/putty/download.html下载并安装 PuTTY。安装后,启动它并确保您允许 Windows 防火墙或任何其他已安装的防火墙打开必要的端口以建立连接。您将根据 Windows 上运行的防火墙软件看到弹出的警告。
启动 PuTTY 后,应用程序将显示PuTTY 配置对话框。在主机名(或 IP 地址)文本框中输入分配给您的板的 IP 地址,并将端口值保留为其默认的22值。以下图片显示了设置以连接到分配 IP 为192.168.1.105的板的对话框。您可以保留默认设置。但是,您绝对应该更改窗口 | 外观设置以更改默认字体。

点击 打开,第一次建立连接时;PuTTY 将显示一个安全警报,因为服务器的主机密钥未缓存在注册表中。您信任您的板和运行在其上的 Yocto Linux,因此只需点击 是。以下图片显示了安全警报。

PuTTY 将显示一个新窗口,具体是一个包含 IP 地址的终端窗口。您将看到以下消息,要求您输入登录用户。
login as:
输入 root 并按 Enter。您将以 root 用户登录,在 Yocto Linux 的默认配置中,该用户不需要密码。现在,您可以运行任何 shell 命令。例如,您可以使用以下命令来检查已安装的 python 版本:
python --version
以下图片显示了以 root 登录并运行了一些命令的 PuTTY 终端窗口:

在 OS X 和 Linux 上,您可以通过打开 终端 并运行 ssh 命令来连接到板上的 Yocto Linux。您必须输入 ssh 后跟一个空格,用户名,一个箭头(@),然后是 IP 地址。在这种情况下,我们想以 root 作为用户名进行连接,因此我们将输入 ssh 后跟一个空格,root@,然后是 IP 地址。以下命令适用于在 192.168.1.105 IP 地址和端口号 22 上运行 SSH 服务器的板。您必须将 192.168.1.105 替换为您检索到的 IP 地址。
ssh root@192.168.1.105
第一次建立连接时,ssh 命令将显示一个安全警报,因为无法验证主机的真实性。您信任您的板和运行在其上的 Yocto Linux,因此对类似以下问题的回答是 yes 并按 Enter。
The authenticity of host '192.168.1.105 (192.168.1.105)' can't be established.
ECDSA key fingerprint is SHA256:Ln7j/g1Np4igsgaUP0ujFC2PPcb1pnkLD8Pk0AK+Vow.
Are you sure you want to continue connecting (yes/no)?
在您回答 yes 并按 Enter 后,ssh 命令将显示类似于以下行的一条消息:
Warning: Permanently added '192.168.1.105' (ECDSA) to the list of known hosts.
在 Yocto Linux 的默认配置中,您将以 root 用户登录,该用户不需要密码。现在,您可以运行任何 shell 命令。例如,您可以使用以下命令来检查已安装的 Python 版本。
python --version
注意,当您看到以下提示 root@galileo:~# 时,这意味着您的所有命令都在板上的 Yocto Linux 上运行,而不是在您的 OS X 终端或 Linux 终端上。以下图片显示了登录为 root 并运行了一些命令的 OS X 终端 窗口:

小贴士
板上启动的 Yocto Linux 预装了 Python 2.7.3。
我们还可以在移动设备上运行任何 SSH 客户端,例如平板电脑或智能手机。有许多为 iOS 和 Android 开发的 SSH 客户端。使用与蓝牙键盘连接的平板电脑工作,并轻松在 SSH 客户端中运行命令是可能的。
安装和升级必要的库以与板子交互
现在,我们将在 SSH 客户端运行许多命令。在运行命令之前,请确保你的 SSH 客户端已连接到板上运行的 Yocto Linux SSH 服务器,如前述章节所述。特别是,如果你在使用 OS X 或 Linux,你必须确保你不在你的计算机上运行命令,而是在远程 shell 上执行此操作。很简单,只需确保在运行任何命令之前,你总是看到提示root@galileo:~#。
小贴士
你的板应该连接到具有互联网访问的 LAN,因为我们将从互联网下载内容。
我们将使用opkg工具下载并安装mraa和upm库的更新版本。mraa库,也称为libmraa,是一个低级别的 C/C++库,具有与 Python 的绑定,使我们能够与 Intel Galileo Gen 2 板和其他支持平台上的 I/O 功能进行接口。upm库为我们可以连接到mraa库支持的平台上的传感器和执行器提供高级接口。upm库简化了与传感器和执行器的工作,并包括 Python 的绑定。在接下来的章节中,我们将使用这两个库,因此,我们希望安装它们的最新版本。
opkg工具是一个轻量级的包管理器,它允许我们轻松下载和安装 OpenWrt 包。OpenWrt 是一种嵌入式设备的 Linux 发行版。首先,我们将使用opkg工具检查 mraa 和 upm 的安装版本。
运行以下命令以检查已安装的 mraa 版本:
opkg info mraa
以下行显示了 mraa 包的版本和依赖项的输出。在这种情况下,输出显示安装的 mraa 版本为0.7.2-r0。
Package: mraa
Version: 0.7.2-r0
Depends: libgcc1 (>= 4.9.1), python-core, libpython2.7-1.0 (>= 2.7.3), libstdc++6 (>= 4.9.1), libc6 (>= 2.20)
Status: install user installed
Architecture: i586
Installed-Time: 1434860546
运行以下命令以检查已安装的 upm 版本:
opkg info upm
以下行显示了 upm 包的版本和依赖项的输出。在这种情况下,输出显示安装的 upm 版本为0.3.1-r0。
Package: upm
Version: 0.3.1-r0
Depends: libgcc1 (>= 4.9.1), libpython2.7-1.0 (>= 2.7.3), libc6 (>= 2.20), python-core, libstdc++6 (>= 4.9.1), mraa (>= 0.7.2)
Status: install user installed
Architecture: i586
Installed-Time: 1434860596
运行以下命令以检查 mraa 和 upm 库的存储库配置。
cat /etc/opkg/mraa-upm.conf
如果你看到以下行作为响应,这意味着存储库已配置为使用 1.5 版本,我们需要更改其配置,以便更新 mraa 和 upm 库到最新版本。
src mraa-upm http://iotdk.intel.com/repos/1.5/intelgalactic
运行以下命令以配置存储库,使 mraa 和 upm 库与版本 2.0 而不是 1.5 一起工作:
echo "src mraa-upm http://iotdk.intel.com/repos/2.0/intelgalactic" > /etc/opkg/mraa-upm.conf
现在,运行以下命令以检查 mraa 和 upm 库的存储库配置,你将注意到输出中已将1.5替换为2.0。
cat /etc/opkg/mraa-upm.conf
你应该看到下一行显示的结果:
src mraa-upm http://iotdk.intel.com/repos/2.0/intelgalactic
我们将使用 opkg 工具从之前配置的位于互联网上的仓库更新软件包。运行以下命令以使 opkg 工具在更改了 mraa 和 upm 库的仓库配置后更新可用软件包列表。
opkg update
之前的命令将生成以下输出,指示已更新的可用软件包列表。请注意,输出中的最后几行指示命令已从 http://iotdk.intel.com/repos/2.0/intelgalactic/Packages 下载,并将可用软件包保存在 /var/lib/opkg/mraa-upm。
Downloading http://iotdk.intel.com/repos/1.5/iotdk/all/Packages.
Updated list of available packages in /var/lib/opkg/iotdk-all.
Downloading http://iotdk.intel.com/repos/1.5/iotdk/i586/Packages.
Updated list of available packages in /var/lib/opkg/iotdk-i586.
Downloading http://iotdk.intel.com/repos/1.5/iotdk/quark/Packages.
Updated list of available packages in /var/lib/opkg/iotdk-quark.
Downloading http://iotdk.intel.com/repos/1.5/iotdk/x86/Packages.
Updated list of available packages in /var/lib/opkg/iotdk-x86.
Downloading http://iotdk.intel.com/repos/2.0/intelgalactic/Packages.
Updated list of available packages in /var/lib/opkg/mraa-upm.
运行以下命令以检查存储在 /var/lib/opkg/mraa-upm 中的 mraa 和 upm 库的版本。
cat /var/lib/opkg/mraa-upm
以下行显示结果。请注意,版本号可能会变化,因为 mraa 和 upm 库都是非常活跃的项目,并且它们经常更新。因此,当你运行之前的命令时,版本号可能会更高。
Package: mraa
Version: 0.9.0
Provides: mraa-dev, mraa-dbg, mraa-doc
Replaces: mraa-dev, mraa-dbg, mraa-doc, libmraa, libmraa-dev, libmraa-doc
Conflicts: mraa-dev, mraa-dbg, mraa-doc
Section: libs
Architecture: i586
Maintainer: Intel IoT-Devkit
MD5Sum: b92167f26a0dc0dba4d485b7bedcfb47
Size: 442236
Filename: mraa_0.9.0_i586.ipk
Source: https://github.com/intel-iot-devkit/mraa
Description: mraa built using CMake
Priority: optional
Package: upm
Version: 0.4.1
Depends: mraa (>= 0.8.0)
Provides: upm-dev, upm-dbg, upm-doc
Replaces: upm-dev, upm-dbg, upm-doc
Conflicts: upm-dev, upm-dbg, upm-doc
Section: libs
Architecture: i586
Maintainer: Intel IoT-Devkit
MD5Sum: 13a0782e478f2ed1e65b33249be41424
Size: 16487850
Filename: upm_0.4.1_i586.ipk
Source: https://github.com/intel-iot-devkit/upm
Description: upm built using CMake
Priority: optional
在这种情况下,我们有 mraa 版本 0.9.0 和 upm 版本 0.4.1。版本号高于最初安装的版本。我们肯定希望将 mraa 0.7.2-r0 升级到 0.9.0,将 upm 0.3.1-r0 升级到 0.4.1。如前所述的行所示,upm 依赖于 mraa 版本 0.8.0 或更高版本,因此我们将首先升级 mraa。
运行以下命令以安装 mraa 库的最新可用版本:
opkg install mraa
以下行显示结果:
Upgrading mraa from 0.7.2-r0 to 0.9.0 on root.
Downloading http://iotdk.intel.com/repos/2.0/intelgalactic/mraa_0.9.0_i586.ipk.
Removing package mraa-dev from root...
Removing package mraa-doc from root...
Removing obsolete file /usr/lib/libmraa.so.0.7.2.
Removing obsolete file /usr/bin/mraa-gpio.
Configuring mraa.
运行以下命令以安装 upm 库的最新可用版本:
opkg install upm
以下行显示一些结果行和最后一行。请注意,包安装会删除大量过时的文件:
Upgrading upm from 0.3.1-r0 to 0.4.1 on root.
Downloading http://iotdk.intel.com/repos/2.0/intelgalactic/upm_0.4.1_i586.ipk.
Removing package upm-dev from root...
Removing obsolete file /usr/lib/libupm-wt5001.so.0.3.1.
Removing obsolete file /usr/lib/libupm-adc121c021.so.0.3.1.
Removing obsolete file /usr/lib/libupm-joystick12.so.0.3.1.
Removing obsolete file /usr/lib/libupm-grove.so.0.3.1.
Removing obsolete file /usr/lib/libupm-tm1637.so.0.3.1.
…
Removing obsolete file /usr/lib/libupm-groveloudness.so.0.3.1.
Configuring upm.
现在,运行以下命令以检查已安装的 mraa 版本:
opkg info mraa
以下行显示带有版本和依赖项的 mraa 包的输出。前几行显示 mraa 版本 0.7.2-r0 已不再安装,而突出显示的行显示已安装 mraa 版本 0.9.0。
Package: mraa
Version: 0.7.2-r0
Depends: libgcc1 (>= 4.9.1), python-core, libpython2.7-1.0 (>= 2.7.3), libstdc++6 (>= 4.9.1), libc6 (>= 2.20)
Status: unknown ok not-installed
Section: libs
Architecture: i586
Maintainer: Intel IoT Devkit team <meta-intel@yoctoproject.org>
MD5Sum: b877585652e4bc34c5d8b0497de04c4f
Size: 462242
Filename: mraa_0.7.2-r0_i586.ipk
Source: git://github.com/intel-iot-devkit/mraa.git;protocol=git;rev=299bf5ab27191e60ea0280627da2161525fc8990
Description: Low Level Skeleton Library for Communication on Intel platforms Low
Level Skeleton Library for Communication on Intel platforms.
Package: mraa
Version: 0.9.0
Provides: mraa-dev, mraa-dbg, mraa-doc
Replaces: mraa-dev, mraa-dbg, mraa-doc, libmraa, libmraa-dev, libmraa-doc
Conflicts: mraa-dev, mraa-dbg, mraa-doc
Status: install user installed
Section: libs
Architecture: i586
Maintainer: Intel IoT-Devkit
MD5Sum: b92167f26a0dc0dba4d485b7bedcfb47
Size: 442236
Filename: mraa_0.9.0_i586.ipk
Source: https://github.com/intel-iot-devkit/mraa
Description: mraa built using CMake
Installed-Time: 1452800349
运行以下命令以检查已安装的 upm 版本:
opkg info upm
以下行给出带有版本和依赖项的 upm 包的输出。前几行显示 upm 版本 0.3.1-r0 已不再安装,而突出显示的行显示已安装 upm 版本 0.4.1。
Package: upm
Version: 0.3.1-r0
Depends: libgcc1 (>= 4.9.1), libpython2.7-1.0 (>= 2.7.3), libc6 (>= 2.20), python-core, libstdc++6 (>= 4.9.1), mraa (>= 0.7.2)
Status: unknown ok not-installed
Section: libs
Architecture: i586
Maintainer: Intel IoT Devkit team <meta-intel@yoctoproject.org>
MD5Sum: 9c38c6a23db13fbeb8c687336d473200
Size: 10344826
Filename: upm_0.3.1-r0_i586.ipk
Source: git://github.com/intel-iot-devkit/upm.git;protocol=git;rev=3d453811fb7760e14da1a3461e05bfba1893c2bd file://0001-adafruitms1438-CMakeLists.txt-stop-RPATH-being-added.patch
Description: Sensor/Actuator repository for Mraa Sensor/Actuator repository for Mraa.
Package: upm
Version: 0.4.1
Depends: mraa (>= 0.8.0)
Provides: upm-dev, upm-dbg, upm-doc
Replaces: upm-dev, upm-dbg, upm-doc
Conflicts: upm-dev, upm-dbg, upm-doc
Status: install user installed
Section: libs
Architecture: i586
Maintainer: Intel IoT-Devkit
MD5Sum: 13a0782e478f2ed1e65b33249be41424
Size: 16487850
Filename: upm_0.4.1_i586.ipk
Source: https://github.com/intel-iot-devkit/upm
Description: upm built using CMake
Installed-Time: 1452800568
现在,我们已经安装了 mraa 和 upm 库的最新版本,我们将能够从任何 Python 程序中使用它们。
安装 pip 和附加库
默认情况下,pip 包管理系统,它使得安装和管理用 Python 编写的软件包变得容易,并未安装。我们将使用 Python 作为我们的主要编程语言,因此,我们肯定会从安装 pip 中受益。
输入以下 curl 命令,从 https://bootstrap.pypa.io 下载 get-pip.py 文件到当前文件夹。
curl -L "https://bootstrap.pypa.io/get-pip.py" > get-pip.py
您将看到类似以下行的输出,这将指示下载进度:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1379k 100 1379k 0 0 243k 0 0:00:05 0:00:05 --:--:-- 411k
下载完成后,运行python并带上get-pip.py作为参数。
python get-pip.py
您将看到类似以下行的输出,这将指示安装进度以及与 SSLContext 相关的一些警告。不要担心这些警告。
Collecting pip
/tmp/tmpe2ukgP/pip.zip/pip/_vendor/requests/packages/urllib3/util/ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
Downloading pip-7.1.2-py2.py3-none-any.whl (1.1MB)
100% |################################| 1.1MB 11kB/s
Collecting wheel
Downloading wheel-0.26.0-py2.py3-none-any.whl (63kB)
100% |################################| 65kB 124kB/s
Installing collected packages: pip, wheel
Successfully installed pip-7.1.2 wheel-0.26.0
/tmp/tmpe2ukgP/pip.zip/pip/_vendor/requests/packages/urllib3/util/ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
现在,我们可以使用pip安装程序轻松地安装额外的 Python 2.7.3 软件包。我们将使用pip安装程序从 Python 包索引 PyPI 获取wiring-x86软件包,并安装它。wiring-x86软件包是一个 Python 模块,它提供了一个类似于 WiringPi 模块的简单 API,用于在 Intel Galileo Gen 2 板和其他支持的平台上的通用 I/O 引脚上使用。我们只需运行以下命令来安装软件包:
pip install wiring-x86
输出的最后几行将指示wiring-x86软件包已成功安装。不要担心与构建wiring-x86轮相关的错误消息。
Installing collected packages: wiring-x86
Running setup.py install for wiring-x86
Successfully installed wiring-x86-1.0.0
调用 Python 解释器
我们已经安装了与 Intel Galileo Gen 2 板中包含的功能交互所需的最重要库的最新版本。现在,我们可以通过输入经典命令来调用 Python 解释器:
python
现在,输入以下两行 Python 代码:
import mraa
mraa.getVersion()
Python 解释器将显示以下输出:
'v0.9.0'
我们导入了mraa库,并调用了mraa.getVersion方法来检查 Python 是否能够检索已安装的mraa库版本。调用该方法的结果显示了为mraa库安装的版本,因此我们知道 Python 将使用我们期望的版本。请注意,Python 代码是在 Intel Galileo Gen 2 板上的 Yocto Linux 上运行的。
现在,输入以下行来检查mraa库是否已成功检测到板类型:
mraa.getPlatformName()
Python 解释器将显示以下输出:
'Intel Galileo Gen 2'
我们调用了mraa.getPlatformName方法,调用该方法的结果显示了我们的板名:Intel Galileo Gen 2。以下截图显示了调用先前方法的结果:

现在,在任何连接到你的局域网中的计算机或设备上打开一个 Web 浏览器,并输入板分配的 IP 地址。例如,如果 IP 地址是192.168.1.104,将其作为 URL 进行浏览。以下截图显示了您将在 Web 浏览器中看到的内容:它工作了!

该板正在作为 Web 服务器运行,并将/www/pages/index.html文件的 内容返回给 Web 浏览器的请求。
测试你的知识
-
我们可以在 Intel Galileo Gen 2 板上访问 Python 2.7.x:
-
在从闪存启动预安装的 SPI 镜像后。
-
在从 microSD 卡启动 Yocto Linux 后,特别是 IoT Devkit 镜像。
-
在启动预安装的 SPI 镜像并按重启按钮三次后。
-
-
一旦 Intel Galileo Gen 2 板连接到我们的局域网,我们就可以使用任何允许我们使用以下接口和协议的实用程序来访问它的 shell:
-
SSH.
-
Telnet.
-
X.25.
-
-
以下哪个库提供了与 Python 的绑定,并允许我们在 Intel Galileo Gen 2 上处理 I/O:
-
IotGalileoGen2.
-
Mraa.
-
Mupm.
-
-
以下哪个包是一个 Python 模块,它提供了一个类似于 WiringPi 模块的 API,用于在 Intel Galieo Gen 2 上使用通用 I/O 引脚:
-
wiring-py-galileo.
-
galileo-gen2-x86.
-
wiring-x86.
-
-
以下哪种方法可以返回 mraa 库自动检测到的板:
-
mraa.getPlatformName().
-
mraa.getBoardName().
-
mraa.getGalileoBoardName().
-
摘要
在本章中,我们遵循了许多程序,使得可以使用 Python 作为主要编程语言,用我们的 Intel Galileo Gen 2 板创建物联网项目。我们将 Linux Yocto 镜像写入到 microSD 卡中,并配置了板子使其能够启动这个镜像,这样我们就可以访问 Python 和其他有用的库来与板子交互。我们更新了许多库以使用它们的最新版本,并启动了 Python 解释器。
现在我们已经准备好用 Python 编写代码的板,我们可以开始将电子组件连接到板上,并使用 Python 和库来写入数字值,这是下一章的主题。
第三章:使用 Python 与数字输出交互
在本章中,我们将使用 Python 和两个库:mraa和wiring-x86来处理数字输入。我们将:
-
将英特尔 Galileo Gen 2 和带有电子组件的面包板之间的第一个连接线焊接好
-
编写一个 Python 脚本的第一个版本,用于控制连接到板上的电子组件的开关
-
将 Python 代码传输到板上的 Yocto Linux 操作系统
-
执行与板交互的 Python 脚本
-
学习利用 Python 的面向对象特性来改进代码并使其更容易理解
-
准备代码,使其易于构建一个 API,允许我们与物联网设备交互
打开和关闭板载组件
首先,我们将利用板载 LED(发光二极管)来编写我们的第一条 Python 代码,这些代码将与我们包含在英特尔 Galileo Gen 2 板中的数字输出功能进行交互。这个简单的例子将帮助我们理解mraa库如何使我们能够通过 Python 代码轻松地打开和关闭板载组件之一。
在上一章中,我们识别了英特尔 Galileo Gen 2 板中包含的不同元素。我们知道有三个矩形 LED 位于 USB 2.0 主机连接器的右侧。第一个 LED,标记为L,连接到数字 I/O 引脚的 13 号引脚,因此,向 13 号引脚发送高电平将打开此 LED,而低电平将关闭它。
我们将编写几行 Python 代码,使用mraa库使标记为L的板载 LED 重复以下循环,直到 Python 程序被中断:
-
打开
-
保持打开 3 秒钟
-
关闭
-
关闭 2 秒钟。
以下行显示了执行先前解释的操作的 Python 代码。示例代码文件为iot_python_chapter_03_01.py。
import mraa
import time
if __name__ == "__main__":
print ("Mraa library version: {0}".format(mraa.getVersion()))
print ("Mraa detected platform name: {0}".format(mraa.getPlatformName()))
# Configure GPIO pin #13 to be an output pin
onboard_led = mraa.Gpio(13)
onboard_led.dir(mraa.DIR_OUT)
while True:
# Turn on the onboard LED
onboard_led.write(1)
print("I've turned on the onboard LED.")
# Sleep 3 seconds
time.sleep(3)
# Turn off the onboard LED
onboard_led.write(0)
print("I've turned off the onboard LED.")
time.sleep(2)
提示
有关下载代码包的详细步骤,请参阅本书的序言。请查看。
书籍的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Internet-of-Things-with-Python。我们还有其他丰富的书籍和视频的代码包,可在github.com/PacktPublishing/找到。请查看它们!
在上一章中,我们了解到在板上运行的 Yocto Linux 通过运行 Bonjour 浏览器提供了SSH和SFTP(简称SSH 文件传输协议或安全文件传输协议)服务。我们可以使用任何 SFTP 客户端连接到板并传输我们在任何计算机或移动设备上创建的文件。当然,我们也可以在 SSH 终端中使用任何 Linux 编辑器,如vi,或者在 Python 解释器中直接输入代码。然而,通常在计算机或移动设备上使用我们喜欢的编辑器或 IDE 然后使用任何 SFTP 客户端将文件传输到板会更方便。
小贴士
一些 Python IDE 具有远程开发功能,并允许我们轻松传输必要的文件并在板上启动它们的执行。一个例子是 JetBrains PyCharm 的付费专业版。不幸的是,社区版不包括此功能。
我们不希望此过程与特定的 IDE 相关联,因此我们将使用 SFTP 客户端传输文件。FileZilla 客户端是一个免费、开源的多平台 FTP 客户端,支持 SFTP。您可以从这里下载和安装它:filezilla-project.org。
一旦您安装并执行了 FileZilla 客户端,您必须按照以下步骤在应用程序的站点管理器中添加板上运行的 SFTP 服务器:
-
选择文件 | 站点管理器。
-
在站点管理器对话框中点击新建站点。输入所需名称,例如IntelGalileo2,以便轻松识别板的 SFTP 服务。
-
在主机中输入板的 IP 地址。您不需要在端口中输入任何值,因为 SFTP 服务器使用默认的 SFTP 端口,即 SSH 守护进程监听的相同端口:端口 22。
-
在协议下拉菜单中选择SFTP - SSH 文件传输协议。
-
在登录类型下拉菜单中选择正常。
-
在用户中输入root。下一张截图显示了 IP 地址分配为192.168.1.107的板的配置值。
![开启和关闭板载组件]()
-
点击连接。FileZilla 将显示一个未知主机密钥对话框,表明服务器的宿主密钥是未知的。这类似于您使用 SSH 客户端首次连接到板时提供的信息。详细信息包括宿主和指纹。激活始终信任此主机,将此密钥添加到缓存复选框,然后点击确定。
-
FileZilla 将在窗口右侧的远程站点下显示 Yocto Linux 运行的
/home/root文件夹。 -
在本地站点下导航到您在本地计算机中保存要传输的 Python 文件的文件夹。
-
选择您想要传输的文件,然后按Enter键将文件传输到板上的
/home/root文件夹。另一种方法是右键单击所需的文件并选择上传。FileZilla 将在远程站点下的/home/root文件夹中显示上传的文件。这样,您将能够访问使用 SSH 终端登录时 Yocto Linux 默认位置中的 Python 文件,即您的root用户的家目录。以下图片显示了使用 FileZilla 上传到/home/root文件夹的许多 Python 文件,并列在/home/root文件夹的内容中。![开启和关闭板载组件]()
小贴士
随着您处理更多项目,您可能需要在/home/root下创建新的文件夹,以在 Yocto Linux 文件系统中为您的 Python 代码提供更好的组织。
下次您需要将文件上传到板上时,您不需要在站点管理器对话框中设置新的站点以建立 SFTP 连接。您只需选择文件 | 站点管理器,在选择条目下选择站点名称,然后点击连接。
如果您在登录后 SSH 终端中运行以下命令,Linux 将打印您的当前文件夹或目录:
pwd
之前命令的结果将是上传 Python 代码文件的同一路径文件夹。
/home/root
一旦我们将文件传输到板上,我们可以在板上的 SSH 终端使用以下命令运行之前的代码:
python iot_python_chapter_03_01.py
之前的代码非常简单。我们使用了多个打印语句来使它更容易理解控制台上的消息。以下几行显示了运行几秒钟后的生成输出:
Mraa library version: v0.9.0
Mraa detected platform name: Intel Galileo Gen 2
Setting GPIO Pin #13 to dir DIR_OUT
I've turned on the onboard LED.
I've turned off the onboard LED.
I've turned on the onboard LED.
I've turned off the onboard LED.
前几行打印了mraa库版本和检测到的平台名称。这样,我们就有了 Python 正在使用的mraa库版本的信息,并确保mraa库已经能够初始化自身并检测到正确的平台:Intel Galileo Gen 2。如果我们遇到特定问题,我们可以使用这些信息来检查与mraa库和检测到的平台相关的特定问题。
下一行创建了一个mraa.Gpio类的实例。GPIO代表通用输入/输出,mraa.Gpio类的实例代表板上的一个通用输入/输出引脚。在这种情况下,我们将13作为pin参数的参数,因此我们创建了一个代表板上 GPIO 引脚编号 13 的mraa.Gpio类的实例。我们将其命名为onboard_led,以便更容易理解该实例允许我们控制板载 LED 的状态。
onboard_led = mraa.Gpio(13)
小贴士
我们只需指定引脚参数的值来初始化 mraa.Gpio 类的一个实例。还有两个额外的可选参数(owner 和 raw),但我们应该将它们保留为默认值。默认情况下,每次我们创建 mraa.Gpio 类的实例时,我们拥有该引脚,并且 mraa 库将在析构时关闭它。
如其名称所示,mraa.Gpio 类的一个实例允许我们以输入或输出方式工作于引脚。因此,为我们的 mraa.Gpio 实例指定所需的方向是必要的。在这种情况下,我们希望将引脚 13 用作输出引脚。以下行调用 dir 方法来配置引脚为输出引脚,即将其方向设置为 mraa.DIR_OUT 值。
onboard_led.dir(mraa.DIR_OUT)
然后,代码无限循环运行,即直到你通过按 Ctrl + C 或在具有远程开发功能的 Python IDE 中使用停止按钮来中断执行为止。
while 循环内的第一行调用 mraa.Gpio 实例 onboard_led 的 write 方法,并将 1 作为 value 必需参数的参数。这样,我们向配置为数字输出的引脚 13 发送高值(1)。因为引脚 13 连有板上 LED,所以在引脚 13 中的高值会导致板上 LED 点亮。
onboard_led.write(1)
在我们点亮 LED 后,一行代码使用 print 语句将消息打印到控制台输出,这样我们知道 LED 应该被点亮。使用 time.sleep 并将 3 作为 seconds 参数的值来延迟执行三秒钟。因为我们没有改变引脚 13 的状态,所以在这段延迟期间 LED 将保持点亮状态。
time.sleep(3)
下一个行调用 mraa.Gpio 实例 onboard_led 的 write 方法,但这次将 0 作为 value 必需参数的参数。这样,我们向配置为数字输出的引脚 13 发送低值(0)。因为引脚 13 连有板上 LED,所以在引脚 13 中的低值会导致板上 LED 关闭。
onboard_led.write(0)
在我们关闭 LED 后,一行代码使用 print 语句将消息打印到控制台输出,这样我们知道 LED 应该被关闭。使用 time.sleep 并将 2 作为 seconds 参数的值来延迟执行 2 秒钟。因为我们没有改变引脚 13 的状态,所以在这段延迟期间 LED 将保持关闭状态。然后,循环重新开始。
小贴士
由于我们可以使用任何 ssh 客户端来运行 Python 代码,因此我们可以看到控制台输出中的 print 语句的结果,这对我们理解数字输出应该发生什么非常有用。我们将在稍后利用 Python 中包含的更高级的日志功能来处理更复杂的情况。
如我们从上一个例子中学到的,mraa库封装了在mraa.Gpio类中与 GPIO 引脚一起工作的所有必要方法。之前的代码没有利用 Python 的面向对象特性,它只是与mraa库中包含的一个类进行了交互。在接下来的例子中,我们将利用许多 Python 特性。此外,一旦我们开始处理更复杂的例子,我们将使板通过网络进行交互。
使用面包板进行原型设计
在上一个例子中,我们与板载 LED 进行了交互,因此我们没有将任何额外的电子元件焊接在板上。现在,是时候转向更复杂的样本了,在这些样本中,我们将不得不开始使用额外的组件和工具。
我们不希望每次想要在板上焊接一些电子元件时都创建一个新的印刷电路板(PCB)并将电子元件焊接在板上。我们将通过这本书进行许多电子项目的原型设计,我们也会在每学完一课后继续进行原型设计,以迈向我们的物联网冒险。因此,我们将使用无焊面包板作为我们电子原型的构建基础。
小贴士
无焊面包板也被称为面包板、无焊插拔面包板或原型板。我们将用它们最简短的名字:面包板。
我们将使用带有 2 条电源线的 830 个接点(连接孔)的面包板来制作所有需要将电子元件焊接在板上的原型。以下图片展示了这种面包板,它由一块大约 6.5" x 2.1"的塑料块组成,上面有许多孔。

以下图片展示了带有 2 条电源线的 830 个接点面包板的内部连接。面包板内部有金属条,它们将孔连接起来,如图片所示。

面包板在板子的顶部和底部提供了两条电源线,总线条或水平总线。这些电源线连接了同一行内的所有孔。每一列有五个行孔相连。
然而,我们必须小心,因为有一些类似的面包板会在中间打断电源线或水平总线,因此,电源线并没有连接同一行内的所有孔。以下图片展示了这类面包板的连接方式。

如果你决定使用这种面包板,你必须将以下连接做到总线上。这样,你将模仿第一个面包板中展示的电线。

我们可以将没有绝缘层的线端插入面包板的孔中,以便接线元件。准备不同长度的跳线和使用不同颜色的电缆都很方便。以下图片显示了多种不同长度的电缆,它们将作为跳线使用。

如果我们不希望花费时间制作自己的跳线,我们可以购买预先制作好的公对公无焊锡柔性面包板跳线,线端带有小插头。
小贴士
您可以使用之前解释过的任何选项来制作本书中我们将要工作的每个示例所需的连接。如果您决定使用公对公面包板跳线,请确保它们是高质量的。
使用原理图进行数字输出接线
现在,是时候利用面包板的原型制作能力,开始制作一个更复杂的示例了。我们将通过使用英特尔 Galileo Gen 2 板的 9 个数字输出,来点亮和熄灭 9 个 LED 灯。每个数字输出将控制一个 LED 灯的点亮或熄灭。
在完成必要的接线后,我们将编写 Python 代码,通过控制数字输出以点亮必要的 LED 灯来从 1 计数到 9。在这种情况下,我们的第一个方法可能不是最好的。然而,在学到了很多东西之后,我们将创建新的版本,并将对初始原型和 Python 代码进行改进。
我们需要以下部件来完成这个示例:
-
三个红色超亮 5mm LED 灯
-
三个白色超亮 5mm LED 灯
-
三个绿色超亮 5mm LED 灯
-
九个 5%容差的 270Ω电阻(红紫棕金)
下面的图示显示了连接到面包板上的组件、必要的接线以及从英特尔 Galileo Gen 2 板到面包板的接线。该示例的 Fritzing 文件为iot_fritzing_chapter_03_02.fzz,以下图片是面包板视图。

在这种情况下,我们决定将 GPIO 引脚号与 LED 号相匹配。这样,无论何时我们想要点亮 LED 1,我们就在 GPIO 引脚号 1 写入高(1)值,无论何时我们想要点亮 LED 2,我们就在 GPIO 引脚号 2 写入高(1)值,以此类推。稍后,我们会意识到这并不是最佳决定,因为由于板上引脚的位置,接线变得比预期的要复杂一些。然而,我们将在稍后分析这种情况,并基于从第一版学到的一切来创建这个示例的新版本。
以下图片显示了带有电子元件表示为符号的原理图。原理图使得理解 Intel Galileo Gen 2 板 GPIO 引脚和电子元件之间的连接变得更容易。显然,原理图得益于 GPIO 引脚号与 LED 号相匹配的事实,这将使得编写我们的第一个版本的代码变得容易。

如前一个原理图所示,板上标记为D1到D9的每个 GPIO 引脚都连接到一个270Ω电阻,该电阻连接到 LED 的正极,每个 LED 的负极连接到地。这样,无论我们向哪个 GPIO 引脚写入高(1)值,板都会在该引脚上施加 5V,LED 将点亮。无论我们向哪个 GPIO 引脚写入低(0)值,板都会在该引脚上施加 0V,LED 将熄灭。
小贴士
由于我们将标记为IOREF的跳线留在了默认的 5V 位置,因此该板将以 5V 为其 GPIO 引脚供电。因此,当我们向其写入高值时,GPIO 引脚将具有 5V。如果我们将此跳线的位置更改为 3.3V,当我们向其写入高值时,GPIO 引脚将具有 3.3V。除非另有说明,否则我们在所有示例中都使用此跳线的默认位置。
现在,是时候将组件插入面包板并完成所有必要的布线了。
小贴士
总是关闭 Yocto Linux,等待所有板载 LED 熄灭,然后从 Intel Galileo Gen 2 板上拔掉电源供应,在添加或从板上的引脚移除任何电线之前。在插入或拔除任何屏蔽之前也这样做。
为了关闭 Yocto Linux,请在您的ssh终端中输入以下命令。确保在输入命令时已经退出了 Python 解释器。
shutdown
上一个命令的结果将显示关闭过程将要开始的时间。消息将与以下输出类似,但日期和时间不同。
Shutdown scheduled for Mon 2016-01-25 23:50:04 UTC, use 'shutdown -c' to cancel.
root@galileo:~#
Broadcast message from root@galileo (Mon 2016-01-25 23:49:04 UTC):
The system is going down for power-off at Mon 2016-01-25 23:50:04 UTC!
然后,等待大约 1 分钟,直到操作系统关闭并且所有板载 LED 熄灭。此时,您可以安全地从板上拔掉电源供应。
在插入 LED 到面包板时,我们必须特别注意。正如我们可以在原理图中看到的那样,电阻连接到 LED 的正极,每个 LED 的负极连接到地。
我们可以很容易地识别 LED 的阳极,即其正极引脚,因为其引脚比另一个引脚稍长。LED 的阴极,即其负极引脚,比另一个引脚短。在以下图片中,LED 的阴极,即其负极引脚是位于左侧(较短的引脚)的引脚。LED 的阳极,即其正极引脚,是位于右侧(稍长的引脚)的引脚。你也可以注意到,连接到 LED 阳极,即其正极引脚的 LED 内部的金属部件比连接到 LED 阴极,即其负极引脚的金属部件小。

图片中的 LED 位于与之前显示的面包板图片中 LED 连接相同的位置。因此,我们必须在面包板上将较短的引脚连接在左侧,较长的引脚连接在右侧。下一张图片显示了面包板图片中的 LED 表示,包括其阴极和阳极。

以下图片显示了 LED 的原理图电子符号,其阴极和阳极的位置与之前显示面包板图片中的位置相同。

以下图片显示了所有连接到面包板的 LED。你可以根据通过 LED 塑料看到的金属部件来检查阴极和阳极。

以下图片显示了所有连接到面包板的 LED,你可以检查 LED 的连接方式与我们之前在 Fritzing 图中的面包板视图看到的一样。

电阻正反方向相同,因此,在面包板上使用它们的方向无关紧要。以下图片显示了一个 270Ω轴向引脚电阻,公差为 5%。请注意,从左到右的颜色带是红色、紫色、棕色和金色。颜色带使我们能够知道电阻的欧姆值和它们的公差值,而无需测量电阻。

以下图片显示了连接到面包板的组件、必要的布线和从英特尔 Galileo Gen 2 板到面包板的布线。

使用 LED、Python 代码和 mraa 库从 1 计数到 9
一旦我们完成布线并确保所有组件和电线都放置正确,我们就可以编写我们的第一个 Python 代码版本,用 LED 从 1 计数到 9,通过 SFTP 将其传输到板子上并执行它。
我们将编写几行 Python 代码,使用mraa库执行以下步骤从 1 计数到 9,每一步之间有 3 秒的暂停:
-
点亮 LED1
-
点亮 LED1 和 LED2
-
点亮 LED1、LED2 和 LED3
-
点亮 LED1、LED2、LED3 和 LED4
-
点亮 LED1、LED2、LED3、LED4 和 LED5
-
点亮 LED1、LED2、LED3、LED4、LED5 和 LED6
-
点亮 LED1、LED2、LED3、LED4、LED5、LED6 和 LED7
-
点亮 LED1、LED2、LED3、LED4、LED5、LED6、LED7 和 LED8
-
点亮 LED1、LED2、LED3、LED4、LED5、LED6、LED7、LED8 和 LED9
以下行显示了执行之前解释的操作的 Python 代码。示例的代码文件为iot_python_chapter_03_02.py。
import mraa
import time
if __name__ == "__main__":
print ("Mraa library version: {0}".format(mraa.getVersion()))
print ("Mraa detected platform name: {0}".format(mraa.getPlatformName()))
# Configure GPIO pins #1 to 9 to be output pins
output = []
for i in range(1, 10):
gpio = mraa.Gpio(i)
gpio.dir(mraa.DIR_OUT)
output.append(gpio)
# Count from 1 to 9
for i in range(1, 10):
print("==== Turning on {0} LEDs ====".format(i))
for j in range(0, i):
output[j].write(1)
print("I've turned on the LED connected to GPIO Pin #{0}.".format(j + 1))
time.sleep(3)
一旦我们将文件传输到板上,我们就可以在板上的 SSH 终端使用以下命令运行之前的代码:
python iot_python_chapter_03_02.py
我们使用了多个print语句,以便通过控制台上的消息使我们更容易理解正在发生的事情。以下行显示了运行代码后生成的输出:
Mraa library version: v0.9.0
Mraa detected platform name: Intel Galileo Gen 2
Setting GPIO Pin #1 to dir DIR_OUT
Setting GPIO Pin #2 to dir DIR_OUT
Setting GPIO Pin #3 to dir DIR_OUT
Setting GPIO Pin #4 to dir DIR_OUT
Setting GPIO Pin #5 to dir DIR_OUT
Setting GPIO Pin #6 to dir DIR_OUT
Setting GPIO Pin #7 to dir DIR_OUT
Setting GPIO Pin #8 to dir DIR_OUT
Setting GPIO Pin #9 to dir DIR_OUT
==== Turning on 1 LEDs ====
I've turned on the LED connected to GPIO Pin #1.
==== Turning on 2 LEDs ====
I've turned on the LED connected to GPIO Pin #1.
I've turned on the LED connected to GPIO Pin #2.
==== Turning on 3 LEDs ====
I've turned on the LED connected to GPIO Pin #1.
I've turned on the LED connected to GPIO Pin #2.
I've turned on the LED connected to GPIO Pin #3.
==== Turning on 4 LEDs ====
I've turned on the LED connected to GPIO Pin #1.
I've turned on the LED connected to GPIO Pin #2.
I've turned on the LED connected to GPIO Pin #3.
I've turned on the LED connected to GPIO Pin #4.
==== Turning on 5 LEDs ====
I've turned on the LED connected to GPIO Pin #1.
I've turned on the LED connected to GPIO Pin #2.
I've turned on the LED connected to GPIO Pin #3.
I've turned on the LED connected to GPIO Pin #4.
I've turned on the LED connected to GPIO Pin #5.
==== Turning on 6 LEDs ====
I've turned on the LED connected to GPIO Pin #1.
I've turned on the LED connected to GPIO Pin #2.
I've turned on the LED connected to GPIO Pin #3.
I've turned on the LED connected to GPIO Pin #4.
I've turned on the LED connected to GPIO Pin #5.
I've turned on the LED connected to GPIO Pin #6.
==== Turning on 7 LEDs ====
I've turned on the LED connected to GPIO Pin #1.
I've turned on the LED connected to GPIO Pin #2.
I've turned on the LED connected to GPIO Pin #3.
I've turned on the LED connected to GPIO Pin #4.
I've turned on the LED connected to GPIO Pin #5.
I've turned on the LED connected to GPIO Pin #6.
I've turned on the LED connected to GPIO Pin #7.
==== Turning on 8 LEDs ====
I've turned on the LED connected to GPIO Pin #1.
I've turned on the LED connected to GPIO Pin #2.
I've turned on the LED connected to GPIO Pin #3.
I've turned on the LED connected to GPIO Pin #4.
I've turned on the LED connected to GPIO Pin #5.
I've turned on the LED connected to GPIO Pin #6.
I've turned on the LED connected to GPIO Pin #7.
I've turned on the LED connected to GPIO Pin #8.
==== Turning on 9 LEDs ====
I've turned on the LED connected to GPIO Pin #1.
I've turned on the LED connected to GPIO Pin #2.
I've turned on the LED connected to GPIO Pin #3.
I've turned on the LED connected to GPIO Pin #4.
I've turned on the LED connected to GPIO Pin #5.
I've turned on the LED connected to GPIO Pin #6.
I've turned on the LED connected to GPIO Pin #7.
I've turned on the LED connected to GPIO Pin #8.
I've turned on the LED connected to GPIO Pin #9.
以下九张图片展示了通过执行 Python 代码在面包板上依次点亮 LED 灯的序列。

首先,代码声明了一个名为output的空列表。然后,一个for循环创建了九个mraa.Gpio类的实例,每个实例代表板上的一个通用输入/输出引脚。我们将i作为pin参数的参数传递,因此每个实例代表 GPIO 引脚的编号等于i。创建实例后,我们调用dir方法将引脚配置为输出引脚,即将其方向设置为mraa.DIR_OUT值。然后我们调用append方法将mraa.Gpio实例(gpio)添加到输出列表中。重要的是要理解range(1, 10)生成以下列表:[1, 2, 3, 4, 5, 6, 7, 8, 9]。因此,我们的for循环将从i等于 1 开始,其最后一次迭代将是i等于 9。
output = []
for i in range(1, 10):
gpio = mraa.Gpio(i)
gpio.dir(mraa.DIR_OUT)
output.append(gpio)
另一个for循环确定要点亮的 LED 灯数量。我们使用range(1, 10)生成与上一个循环相同的列表。for循环内的第一行调用print方法来显示在迭代中将要点亮的 LED 灯的数量。循环内的循环使用range(0, i)生成output列表中要点亮的元素的索引列表,这是主for循环迭代(i)的一部分。
内层循环使用j作为其变量,内层循环中的代码只是为每个mraa.Gpio实例,output[j],调用write方法,并将1作为value必需参数的参数。这样,我们向等于j + 1的引脚发送高值(1),该引脚配置为数字输出。如果j等于 0,输出列表的第一个元素是配置为引脚 1(j + 1)的mraa.Gpio实例。因为从 1 到 9 的每个引脚都连接了一个 LED,所以一个或多个引脚上的高值将导致 LED 打开。然后,代码打印一条消息,指示已打开的 LED 编号。
内层循环完成后,调用time.sleep并使用3作为seconds参数的值,将执行延迟三秒钟。这样,LED 或 LEDs 在延迟期间保持打开状态,然后外层循环执行另一个迭代。
for i in range(1, 10):
print("==== Turning on {0} LEDs ====".format(i))
for j in range(0, i):
output[j].write(1)
print("I've turned on the LED connected to GPIO Pin #{0}.".format(j + 1))
time.sleep(3)
以下图片显示了在笔记本电脑的 SSH 终端上打印的控制台输出,连接到运行 Python 代码的板的电路板上 9 个 LED 打开。

利用面向对象代码控制数字输出
之前的示例只是打开了 LED。因此,如果我们想按逆序计数,即从 9 到 1,结果将不会如预期。代码打开 9 个 LED 后,代码将打开 8 个 LED,但仍然会有 9 个 LED 打开。问题是我们从未关闭不需要打开的 LED,因此 9 个 LED 将保持打开状态,直到编辑的循环执行完毕。
我们一直在谈论打开 LED 和关闭 LED。然而,我们一直在使用mraa.Gpio类的实例并调用write方法。Python 是一种面向对象的编程语言,因此我们可以充分利用其面向对象的功能来编写可重用、易于理解和易于维护的代码。例如,在这种情况下,创建一个Led类来表示连接到我们的板上的 LED 非常有意义。
以下行显示了新Led类的代码。示例的代码文件为iot_python_chapter_03_03.py。
import mraa
import time
class Led:
def __init__(self, pin):
self.gpio = mraa.Gpio(pin)
self.gpio.dir(mraa.DIR_OUT)
def turn_on(self):
self.gpio.write(1)
print("I've turned on the LED connected to GPIO Pin #{0}.".format(self.gpio.getPin()))
def turn_off(self):
self.gpio.write(0)
print("I've turned off the LED connected to GPIO Pin #{0}.".format(self.gpio.getPin()))
当我们在pin必需参数中创建Led类的实例时,我们必须指定 LED 连接的引脚号。构造函数,即__init__方法,使用接收到的pin作为其pin参数创建一个新的mraa.Gpio实例,将其引用保存在gpio属性中,并调用其dir方法来配置引脚为输出引脚。
该类定义了以下两个方法:
-
turn_on:调用相关mraa.Gpio实例的write方法,向引脚发送高值(1)并打开连接到此引脚的 LED。然后,它打印一条包含执行动作详细信息的消息。 -
turn_off:调用相关mraa.Gpio实例的write方法,向引脚发送低值(0)并关闭连接到该引脚的 LED 灯。然后,它打印一条包含执行动作详细信息的消息。
现在,我们可以编写使用新Led类的代码,根据我们想要控制的 LED 灯数量和它们连接的引脚来创建必要的实例。以下行显示了使用新Led类从 1 到 9 计数并使用 LED 灯的改进代码版本。示例代码文件为iot_python_chapter_03_03.py。
if __name__ == "__main__":
print ("Mraa library version: {0}".format(mraa.getVersion()))
print ("Mraa detected platform name: {0}".format(mraa.getPlatformName()))
# Configure GPIO pins #1 to 9 to be output pins
leds = []
for i in range(1, 10):
led = Led(i)
leds.append(led)
# Count from 1 to 9
for i in range(1, 10):
print("==== Turning on {0} LEDs ====".format(i))
for j in range(0, i):
leds[j].turn_on()
for k in range(i, 9):
leds[k].turn_off()
time.sleep(3)
首先,代码声明了一个名为leds的空列表。然后,一个for循环创建了 9 个Led类的实例,每个实例代表连接到板上 GPIO 引脚的一个 LED 灯。我们将i作为pin参数的参数传递。然后,我们调用leds列表的append方法将Led实例(led)添加到leds列表中。我们的for循环将从i等于 1 开始,其最后一次迭代将是i等于 9。
另一个for循环确定要开启的 LED 灯数量。我们使用range(1, 10)来生成与上一个循环相同的列表。for循环中的第一行调用print方法来显示在迭代中将要开启的 LED 灯数量。
循环内的内循环使用range(0, i)来生成leds列表中要开启的元素的索引列表,以便在主for循环的迭代中(i)。内循环使用j作为其变量,并且这个内循环中的代码只是调用每个Led实例的turn_on方法。
循环内的另一个内循环使用range(i, 9)来生成leds列表中要关闭的元素的索引列表,以便在主for循环的迭代中(i)。内循环使用k作为其变量,并且这个内循环中的代码只是调用每个Led实例的turn_off方法。
小贴士
与上一个版本相比,代码更容易理解,Led类处理与 LED 灯相关的所有事情。我们可以轻松理解调用leds[j]的turn_on方法的行是在开启一个 LED 灯。我们肯定知道在调用leds[k]的turn_off方法的行中,一个 LED 灯正在关闭。
由于新代码关闭了不需要开启的 LED 灯,我们可以轻松地通过更改一行来创建一个从 9 倒数到 1 的新版本。以下行显示了使用Led类从 9 倒数到 1 并使用 LED 灯的新代码版本。需要编辑的唯一一行是高亮显示的那一行。示例代码文件为iot_python_chapter_03_04.py。
if __name__ == "__main__":
print ("Mraa library version: {0}".format(mraa.getVersion()))
print ("Mraa detected platform name: {0}".format(mraa.getPlatformName()))
# Configure GPIO pins #1 to 9 to be output pins
leds = []
for i in range(1, 10):
led = Led(i)
leds.append(led)
# Count from 9 to 1
for i in range(9, 0, -1):
print("==== Turning on {0} LEDs ====".format(i))
for j in range(0, i):
leds[j].turn_on()
for k in range(i, 9):
leds[k].turn_off()
time.sleep(3)
改进我们的面向对象代码以提供新功能
现在我们已经让计数器与连接到板上的 LED 灯正常工作,我们想要添加新功能。我们希望能够轻松地将 1 到 9 之间的数字转换为其连接到板上的 LED 灯的表示形式。
以下行显示了新NumberInLeds类的代码。示例的代码文件为iot_python_chapter_03_05.py。
class NumberInLeds:
def __init__(self):
self.leds = []
for i in range(1, 10):
led = Led(i)
self.leds.append(led)
def print_number(self, number):
print("==== Turning on {0} LEDs ====".format(number))
for j in range(0, number):
self.leds[j].turn_on()
for k in range(number, 9):
self.leds[k].turn_off()
构造函数,即__init__方法,声明了一个名为leds的空列表属性(self.leds)。然后,一个for循环创建了九个Led类的实例,每个实例代表连接到板上 GPIO 引脚的一个 LED。我们将i作为pin参数的参数传递。然后,我们调用self.leds列表的append方法将Led实例(led)添加到self.leds列表中。我们的for循环将从i等于 1 开始,其最后一次迭代将是i等于 9。
该类定义了一个print_number方法,该方法需要一个数字作为number参数,我们希望用 LED 点亮来表示这个数字。该方法使用一个for循环,其中j作为其变量,通过访问self.leds列表的适当成员并调用turn_on方法来点亮必要的 LED。然后,该方法使用另一个for循环,其中k作为其变量,通过访问self.leds列表的适当成员并调用turn_off方法来关闭剩余的 LED。这样,该方法确保只有需要点亮的 LED 真正点亮,其余的 LED 都关闭。
现在,我们可以编写使用新NumberInLeds类来使用 LED 从 0 到 9 计数的代码。在这种情况下,我们从 0 开始,因为新类能够关闭所有不应该点亮的 LED 来表示特定的数字。示例的代码文件为iot_python_chapter_03_05.py。
if __name__ == "__main__":
print ("Mraa library version: {0}".format(mraa.getVersion()))
print ("Mraa detected platform name: {0}".format(mraa.getPlatformName()))
number_in_leds = NumberInLeds()
# Count from 0 to 9
for i in range(0, 10):
number_in_leds.print_number(i)
time.sleep(3)
代码非常容易理解,我们只需创建一个名为number_in_leds的NumberInLeds类实例,然后在for循环中将其print_number方法作为参数调用i。
小贴士
我们利用 Python 的面向对象特性来创建代表 LED 和 LED 生成数字的类。这样,我们编写了更高级别的代码,更容易理解,因为我们不仅读取将 0 和 1 写入特定引脚号的代码,我们还可以读取打印 LED 上数字的代码,以及打开和关闭 LED 的代码。
将引脚号隔离以改善布线
显然,当连接到 GPIO 引脚 1 时,点亮表示数字 1 的 LED 很容易。在我们之前的布线中,代表每个数字的 LED 都连接到相同的 GPIO 引脚号。该电路图也很容易理解,因为 LED 编号与引脚编号相匹配。
然而,板子和面包板之间的连接有点复杂,因为板上的 GPIO 引脚从 13 向下到 1,从左到右。面包板上的 LED 方向相反,即从 1 到 9,从左到右。因此,连接 GPIO 引脚编号 1 和 LED 编号 1 的线必须从右到左,并穿过其他跳线。我们将更改跳线以改进我们的布线,然后我们将对我们的面向对象的 Python 代码进行必要的更改,以隔离引脚编号,使其布线更美观。在更改布线之前,不要忘记关闭操作系统并从板上拔掉电源。
以下图表显示了连接到面包板和从 Intel Galileo Gen 2 板到面包板的新布线的组件。示例的 Fritzing 文件是iot_fritzing_chapter_03_06.fzz,以下图片是面包板视图。

现在,无论何时我们想要打开 LED 1,我们必须将 GPIO 引脚编号 9 写入高(1)值,无论何时我们想要打开 LED 2,我们写入 GPIO 引脚编号 8 的高(1)值,依此类推。由于我们更改了布线,表示电子组件为符号的原理图也发生了变化。以下图片显示了新版本的原理图。

以下行显示了Led类的新代码。示例的代码文件是iot_python_chapter_03_06.py。
import mraa
import time
class Led:
def __init__(self, pin, position):
self.position = position
self.gpio = mraa.Gpio(pin)
self.gpio.dir(mraa.DIR_OUT)
def turn_on(self):
self.gpio.write(1)
print("I've turned on the LED connected to GPIO Pin #{0}, in position {1}.".format(self.gpio.getPin(), self.position))
def turn_off(self):
self.gpio.write(0)
print("I've turned off the LED connected to GPIO Pin #{0}, in position {1}.".format(self.gpio.getPin(), self.position))
现在,当我们创建Led类的实例时,必须指定一个额外的参数:面包板上的position,即面包板上的 LED 编号。构造函数,即__init__方法,将position值保存在具有相同名称的属性中。turn_on和turn_off方法都使用self.position属性值来打印一条消息,指示已打开或关闭的 LED 的位置。由于位置不再与引脚匹配,因此必须改进消息以指定位置。
以下行显示了新版本的NumberInLeds类的代码。示例的代码文件是iot_python_chapter_03_06.py。
class NumberInLeds:
def __init__(self):
self.leds = []
for i in range(9, 0, -1):
led = Led(i, 10 - i)
self.leds.append(led)
def print_number(self, number):
print("==== Turning on {0} LEDs ====".format(number))
for j in range(0, number):
self.leds[j].turn_on()
for k in range(number, 9):
self.leds[k].turn_off()
在构造函数中,即__init__方法中,有必要对高亮显示的行进行修改。现在创建Led类九个实例的for循环从i等于 9 开始,其最后一次迭代将是i等于 1。我们将i作为pin参数的参数传递,将10 – i作为位置参数的参数传递。这样,self.leds列表中的第一个Led实例将是引脚等于 9 且位置等于 1 的实例。
使用新版本的NumberInLeds类从 0 到 9 计数并使用 LED 的代码与之前的代码相同。示例的代码文件是iot_python_chapter_03_06.py。
if __name__ == "__main__":
print ("Mraa library version: {0}".format(mraa.getVersion()))
print ("Mraa detected platform name: {0}".format(mraa.getPlatformName()))
number_in_leds = NumberInLeds()
# Count from 0 to 9
for i in range(0, 10):
number_in_leds.print_number(i)
time.sleep(3)
我们只需要在封装 LED 的类(Led)和封装用 LED 表示的数字的类(NumberInLeds)中进行一些更改。以下图片显示了面包板上的 9 个 LED 在新布线连接到运行新 Python 代码的板之间打开的情况。

我们可以轻松构建一个 API 并提供一个 REST API,允许任何连接到板的客户端能够通过 HTTP 打印数字。我们的 REST API 只需要创建一个 NumberInLeds 类的实例,并使用指定的数字调用 print_number 方法来通过 LED 打印。我们将在下一章构建这个 REST API。
使用 wiring-x86 库控制数字输出
使用 Python 作为我们的编程语言与板交互的一个巨大优势是我们有大量的 Python 包可用。我们一直在使用 mraa 库与数字输出交互。然而,在上一章中,我们也安装了 wiring-x86 库。我们只需更改几行面向对象的代码,用 wiring-x86 库替换 mraa 库来打开和关闭 LED。
以下行显示了 Board 类的代码,以及与 wiring-x86 库一起工作的 Led 类的新版本,而不是使用 mraa。示例代码文件为 iot_python_chapter_03_07.py。
from wiringx86 import GPIOGalileoGen2 as GPIO
import time
class Board:
gpio = GPIO(debug=False)
class Led:
def __init__(self, pin, position):
self.pin = pin
self.position = position
self.gpio = Board.gpio
self.gpio.pinMode(pin, self.gpio.OUTPUT)
def turn_on(self):
self.gpio.digitalWrite(self.pin, self.gpio.HIGH)
print("I've turned on the LED connected to GPIO Pin #{0}, in position {1}.".format(self.pin, self.position))
def turn_off(self):
self.gpio.digitalWrite(self.pin, self.gpio.LOW)
print("I've turned off the LED connected to GPIO Pin #{0}, in position {1}.".format(self.pin, self.position))
wiring-x86 库不包括自动检测板的功能,因此,有必要使用代表我们板的类。GPIOGalileoGen2 代表英特尔 Galileo Gen 2 板,因此,代码的第一行使用一个 import 语句将其导入为 GPIO 从 wiringx86。这样,每次我们引用 GPIO 时,我们实际上是在使用 wiringx86.GPIOGalileoGen2。请注意,库的名称是 wiring-x86,但模块的名称是 wiringx86。
当我们创建 Led 类的实例时,我们必须指定 LED 连接的 GPIO 数字 pin 和在面包板上的 position,即面包板上的 LED 号码。构造函数,即 __init__ 方法,将 Board.gpio 类属性引用保存到 self.gpio 中,并调用其 pinMode 方法,将接收到的引脚作为其 pin 参数,将 self.gpio.OUTPUT 作为其 mode 参数。这样,我们配置引脚为输出引脚。所有的 Led 实例都将保存对创建 GPIO 类实例的同一 Board.gpio 类属性的引用,特别是具有 debug 参数设置为 False 的 wiringx86.GPIOGalileoGen2 类,以避免低级通信中的不必要调试信息。
turn_on方法调用 GPIO 实例的digitalWrite方法,将高值(self.GPIO.HIGH)发送到由self.pin属性值指定的针脚,并打印关于执行动作的消息。
turn_off方法调用 GPIO 实例的digitalWrite方法,将低值(self.GPIO.LOW)发送到由self.pin属性值指定的针脚,并打印关于执行动作的消息。
NumberInLeds类的代码与之前示例中使用的相同。无需对此类进行更改,因为它将自动与新Led类一起工作,并且其构造函数或两个方法的参数没有发生变化。我们只需替换__main__方法中打印有关mraa库信息的行,因为我们不再使用mraa库。
以下行显示了NumberInLeds类和__main__方法的代码。示例的代码文件为iot_python_chapter_03_07.py。
class NumberInLeds:
def __init__(self):
self.leds = []
for i in range(9, 0, -1):
led = Led(i, 10 - i)
self.leds.append(led)
def print_number(self, number):
print("==== Turning on {0} LEDs ====".format(number))
for j in range(0, number):
self.leds[j].turn_on()
for k in range(number, 9):
self.leds[k].turn_off()
if __name__ == "__main__":
print ("Working with wiring-x86 on Intel Galileo Gen 2")
number_in_leds = NumberInLeds()
# Count from 0 to 9
for i in range(0, 10):
number_in_leds.print_number(i)
time.sleep(3)
我们只需更改几行代码,就可以看到 Python 代码如何使用wiring-x86库在面包板上使 LED 从 0 计数到 9。我们使用此库与 GPIO 针脚进行数字输出的方式与mraa库中使用的机制略有不同。然而,我们可以通过利用 Python 的面向对象特性轻松封装这些更改。我们可以根据自己的喜好和需求决定哪个库更适合我们的项目。拥有不止一个选项总是一个好主意。
测试你的知识
-
当我们将高值(1)发送到配置为输出的 GPIO 针脚时,该 GPIO 针脚将具有:
-
0 V。
-
6 V。
-
IOREF 跳线所在位置的电压值。
-
-
mraa.Gpio类的实例表示:-
板上单个 GPIO 针脚。
-
板上的所有 I/O 针脚。
-
板上的两个 GPIO 针脚。
-
-
当我们创建
mraa.Gpio类的实例时,我们必须指定:-
针脚号作为参数。
-
具体的板和针脚号作为参数。
-
针脚号和期望的方向:
mraa.DIR_OUT或mraa.DIR_IN。
-
-
以下哪行将高值写入配置为输出的 GPIO 针脚的
mraa.Gpio实例名为gpio10:-
gpio10.write(0) -
gpio10.write(1) -
gpio10.write(mraa.HIGH_VALUE)
-
-
以下哪行配置了名为
gpio10的mraa.Gpio实例以进行数字输出:-
gpio10.dir(mraa.DIR_DIGITAL).out() -
gpio10.dir(mraa.DIR_OUT) -
gpio10.dir(mraa.DIR_OUT, mraa.DIGITAL)
-
摘要
在本章中,我们使用 Python 与两个不同的库:mraa和wiring-x86。我们将 LED 和电阻连接到面包板上,并编写了代码来从 0 到 9 点亮 LED。我们改进了 Python 代码,以利用 Python 的面向对象特性,并准备了代码,使其易于构建一个 REST API,允许我们使用 LED 打印数字。
现在我们完成了第一次布线,并开始用 Python 控制板,我们可以开始使用额外的输出,并将它们与 REST API 结合,这是下一章的主题。
第四章. 使用 RESTful API 和脉冲宽度调制
在本章中,我们将通过 HTTP 请求与板子交互,并使用脉冲宽度调制来生成不同的输出电压。我们将:
-
使用 Tornado 网络服务器在 Python 中构建 RESTful API
-
组合并发送 HTTP 请求以在 LED 上打印数字
-
使用脉冲宽度调制控制引脚上的输出电压
-
淡入和淡出连接到板子的 LED
-
使用不同的工具来组合和发送与板子交互的 HTTP 请求
-
使用 RGB LED 构建 RESTful API 来混合红色、绿色和蓝色,并生成数百万种颜色
-
使用
mraa和wiring-x86库来控制脉冲宽度调制
使用 RESTful API 在 LED 上打印数字
Tornado 是一个 Python 网络框架和异步网络库。它因其非阻塞网络 I/O 而广为人知,提供了出色的可伸缩性。我们将利用 Tornado 使得构建 RESTful API 变得非常容易,并使任何客户端都能消费这个 API,并在连接到板子的 LED 上打印数字。以下是为 Tornado 网络服务器提供的网页:www.tornadoweb.org。
在第一章理解和设置基础物联网硬件中,我们安装了pip安装程序,以便在板子上运行的 Yocto Linux 中轻松安装额外的 Python 2.7.3 包。现在,我们将使用pip安装程序来安装 Tornado 4.3。我们只需在 SSH 终端中运行以下命令即可安装该包。
pip install tornado
输出的最后几行将指示tornado包已成功安装。不要担心与构建轮和平台不安全警告相关的错误消息。
Collecting tornado
/usr/lib/python2.7/site-packages/pip/_vendor/requests/packages/urllib3/util/ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
InsecurePlatformWarning
Downloading tornado-4.3.tar.gz (450kB)
100% |################################| 454kB 25kB/s
Collecting backports.ssl-match-hostname (from tornado)
Downloading backports.ssl_match_hostname-3.5.0.1.tar.gz
Collecting singledispatch (from tornado)
Downloading singledispatch-3.4.0.3-py2.py3-none-any.whl
Collecting certifi (from tornado)
Downloading certifi-2015.11.20.1-py2.py3-none-any.whl (368kB)
100% |################################| 372kB 31kB/s
Collecting backports-abc>=0.4 (from tornado)
Downloading backports_abc-0.4-py2.py3-none-any.whl
Collecting six (from singledispatch->tornado)
Downloading six-1.10.0-py2.py3-none-any.whl
...
Installing collected packages: backports.ssl-match-hostname, six, singledispatch, certifi, backports-abc, tornado
Running setup.py install for backports.ssl-match-hostname
Running setup.py install for tornado
Successfully installed backports-abc-0.4 backports.ssl-match-hostname-3.5.0.1 certifi-2015.11.20.1 singledispatch-3.4.0.3 six-1.10.0 tornado-4.3
现在,我们将安装 HTTPie,这是一个用 Python 编写的命令行 HTTP 客户端,它使得发送 HTTP 请求变得容易,并使用比 curl(也称为 cURL)更简单的语法。HTTPie 显示彩色输出,这将使我们能够轻松发送 HTTP 请求来测试我们的 RESTful API。我们只需在 SSH 终端中运行以下命令即可安装该包。
pip install --upgrade httpie
输出的最后几行将指示httpie包已成功安装。不要担心平台不安全警告。
Collecting httpie
/usr/lib/python2.7/site-packages/pip/_vendor/requests/packages/urllib3/util/ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
InsecurePlatformWarning
Downloading httpie-0.9.3-py2.py3-none-any.whl (66kB)
100% |################################| 69kB 117kB/s
Collecting Pygments>=1.5 (from httpie)
Downloading Pygments-2.0.2-py2-none-any.whl (672kB)
100% |################################| 675kB 17kB/s
Collecting requests>=2.3.0 (from httpie)
Downloading requests-2.9.1-py2.py3-none-any.whl (501kB)
100% |################################| 503kB 23kB/s
Installing collected packages: Pygments, requests, httpie
Successfully installed Pygments-2.0.2 httpie-0.9.3 requests-2.9.1
现在,我们可以使用 http 命令轻松地向 localhost 发送 HTTP 请求并测试使用 Tornado 构建的 RESTful API。显然,在我们测试本地 RESTful API 正常工作后,我们希望从连接到我们局域网的计算机或设备发送 HTTP 请求。你可以在你的计算机上安装 HTTPie 或使用任何其他允许你编写和发送 HTTP 请求的应用程序,例如之前提到的 curl 工具(curl.haxx.se) 或在 Windows 系统上使用的 Telerik Fiddler(www.telerik.com/fiddler)。Telerik Fiddler 是一个带有图形用户界面的免费网页调试代理,但它只能在 Windows 上运行。你甚至可以使用能够从移动设备编写和发送 HTTP 请求的应用程序,并通过它们测试 RESTful API。
提示
如果你正在使用 OS X 或 Linux 系统,你可以打开终端并从命令行开始使用 curl。如果你正在使用 Windows 系统,你可以轻松地从 Cygwin 软件包安装选项中安装 curl,并在 Cygwin 终端中执行它。
为了使用 Tornado 构建 RESTful API,我们首先必须创建 tornado.web.RequestHandler 类的子类并重写必要的处理 HTTP 请求到 URL 的方法。例如,如果我们想处理一个同步操作的 HTTP GET 请求,我们必须创建 tornado.web.RequestHandler 类的新子类并定义带有所需参数的 get 方法(如果有)。如果我们想处理 HTTP PUT 请求,我们只需定义带有所需参数的 put 方法。然后,我们必须在 tornado.web.Application 类的实例中映射 URL 模式。
以下几行展示了我们必须添加到现有代码中的新类,无论是使用 mraa 还是 wiring-x86 库,这些库使得在前一章中在 LED 中打印数字成为可能。我们已经有 Led 和 NumberInLeds 类,代码添加了以下类:BoardInteraction、VersionHandler、PutNumberInLedsHandler、GetCurrentNumberHandler。示例代码文件为 iot_python_chapter_04_01.py。
import mraa
from datetime import date
import tornado.escape
import tornado.ioloop
import tornado.web
class BoardInteraction:
number_in_leds = NumberInLeds()
current_number = 0
class VersionHandler(tornado.web.RequestHandler):
def get(self):
response = {'version': '1.0',
'last_build': date.today().isoformat()}
self.write(response)
class PutNumberInLedsHandler(tornado.web.RequestHandler):
def put(self, number):
int_number = int(number)
BoardInteraction.number_in_leds.print_number(int_number)
BoardInteraction.current_number = int_number
response = {'number': int_number}
self.write(response)
class GetCurrentNumberHandler(tornado.web.RequestHandler):
def get(self):
response = {'number': BoardInteraction.current_number}
self.write(response)
BoardInteraction 类声明了两个类属性:number_in_leds 和 current_number。其他类定义了与这些类属性一起工作的方法,以访问保存在 number_in_leds 中的公共 NumberInLeds 实例,以及保存在 current_number 中的当前通过 LED 显示的数字。
代码声明了以下三个 tornado.web.RequestHandler 的子类:
-
VersionHandler:定义了一个无参数的get方法,该方法返回包含版本号和最后构建日期的响应。 -
PutNumberInLedsHandler: 定义了一个需要数字参数的put方法,该参数指定了必须通过 LED 打印的数字。该方法调用存储在BoardInteraction.number_in_leds类属性中的NumberInLeds实例的print_number方法,并使用在number属性中指定的要开启的 LED 数量。然后,代码将正在打印的数字保存到BoardInteraction.current_number类属性中,并返回一个包含打印数字的响应。 -
GetCurrentNumberHandler: 定义了一个参数较少的get方法,该方法返回一个包含BoardInteraction.current_number类属性值的响应,即正在通过 LED 打印的数字。
下面的行使用先前声明的 tornado.web.RequestHandler 的子类来使用 Tornado 构建网络应用程序,代表 RESTful API 和新的 __main__ 方法。示例代码文件为 iot_python_chapter_04_01.py。
application = tornado.web.Application([
(r"/putnumberinleds/([0-9])", PutNumberInLedsHandler),
(r"/getcurrentnumber", GetCurrentNumberHandler),
(r"/version", VersionHandler)])
if __name__ == "__main__":
print("Listening at port 8888")
BoardInteraction.number_in_leds.print_number(0)
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
首先,代码创建了一个名为 application 的 tornado.web.Application 类实例,其中包含组成网络应用程序的请求处理器列表。代码将一个元组列表传递给 Application 构造函数。该列表由正则表达式(regexp)和 tornado.web.RequestHandler 的子类(request_class)组成。
__main__ 方法打印一条消息,指示 HTTP 服务器正在监听的端口号,并使用保存在 BoardInteraction.number_in_leds 中的 NumberInLeds 实例打印数字 0,即关闭九个 LED。下一行调用 application.listen 方法,在指定的端口上为应用程序构建一个具有定义规则的 HTTP 服务器。代码将 8888 作为 port 参数传递,即 Tornado HTTP 服务器的默认端口号。
然后,调用 tornado.ioloop.IOLoop.instance().start() 启动了使用 application.listen 创建的服务器。这样,每当网络应用程序收到请求时,Tornado 会遍历组成网络应用程序的请求处理器列表,并为第一个与请求路径匹配的关联正则表达式的 tornado.web.RequestHandler 子类创建一个实例。然后,Tornado 根据 HTTP 请求调用以下方法之一,并基于新实例的相应参数:
-
head -
get -
post -
delete -
patch -
put -
options
下表显示了与前面代码中定义的正则表达式匹配的一些 HTTP 请求。在这种情况下,HTTP 请求使用 localhost,因为它们是在板上运行的 Yocto Linux 本地执行的。如果我们用板的分配 IP 地址替换 localhost,我们就可以从任何连接到我们的 LAN 的计算机或设备发出 HTTP 请求。
| HTTP 动词和请求 URL | 匹配请求路径的元组(regexp,request_class) |
被调用的 RequestHandler 子类和方法 |
|---|---|---|
GET http://localhost:8888/version |
(r"/version", VersionHandler)]) |
VersionHandler.get() |
PUT http://localhost:8888/putnumberinleds/5 |
(r"/putnumberinleds/([0-9])", PutNumberInLedsHandler) |
PutNumberInLedsHandler.put(5) |
PUT http://localhost:8888/putnumberinleds/8 |
(r"/putnumberinleds/([0-9])", PutNumberInLedsHandler) |
PutNumberInLedsHandler.put(8) |
GET http://localhost:8888/getcurrentnumber |
(r"/getcurrentnumber", GetCurrentNumberHandler) |
GetCurrentNumberHandler.get() |
RequestHandler类声明了一个SUPPORTED_METHODS类属性,以下代码。在这种情况下,我们没有重写类属性,因此,我们继承了超类的声明:
SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT", "OPTIONS")
在超类中声明的get、head、post、delete、patch、put和options方法的默认代码是一行,它会引发HTTPError。例如,以下行显示了在RequestHandler类中定义的get方法的代码。
def get(self, *args, **kwargs):
raise HTTPError(405)
无论何时,Web 应用程序收到请求并与 URL 模式匹配,Tornado 都会执行以下操作:
-
创建一个映射到 URL 模式的
RequestHandler子类的实例。 -
使用应用程序配置中指定的关键字参数调用
initialize方法。我们可以重写initialize方法,将参数保存到成员变量中。 -
无论哪个 HTTP 请求,调用
prepare方法。如果我们调用finish或send_error,Tornado 不会调用任何其他方法。我们可以重写prepare方法来执行任何 HTTP 请求所需的代码,然后在get、head、post、delete、patch、put或options方法中编写特定的代码。 -
根据基于 URL 正则表达式捕获的不同组调用方法。如前所述,我们必须重写我们希望我们的
RequestHandler子类能够处理的方法。例如,如果有 HTTPGET请求,Tornado 将调用get方法并传递不同的参数。 -
在这种情况下,我们正在使用同步处理器,因此,Tornado 在根据 HTTP 请求返回之前的方法调用后调用
on_finish。我们可以重写on_finish方法来执行清理或记录。非常重要的一点是,Tornado 在向客户端发送响应后调用on_finish。
以下行将在板上的 Yocto Linux 上启动 HTTP 服务器和我们的 RESTful API。不要忘记,你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux,正如前一章所述。
python iot_python_chapter_04_01.py
在我们启动 HTTP 服务器后,我们将看到以下输出,并且板上的所有 LED 都将关闭。
Listening at port 8888
==== Turning on 0 LEDs ====
I've turned off the LED connected to GPIO Pin #9, in position 1.
I've turned off the LED connected to GPIO Pin #8, in position 2.
I've turned off the LED connected to GPIO Pin #7, in position 3.
I've turned off the LED connected to GPIO Pin #6, in position 4.
I've turned off the LED connected to GPIO Pin #5, in position 5.
I've turned off the LED connected to GPIO Pin #4, in position 6.
I've turned off the LED connected to GPIO Pin #3, in position 7.
I've turned off the LED connected to GPIO Pin #2, in position 8.
I've turned off the LED connected to GPIO Pin #1, in position 9.
组成和发送 HTTP 请求
HTTP 服务器正在 Yocto Linux 上运行,等待我们的 HTTP 请求来控制连接到 Intel Galileo Gen 2 板上的 LED。现在,我们将在 Yocto Linux 本地编写和发送 HTTP 请求,然后从其他计算机或设备发送,这些设备连接到我们的局域网。
HTTPie 支持类似 curl 的本地主机缩写。例如,:8888 是一个展开为 http://localhost:8888 的缩写。我们已经在另一个 SSH 终端中运行了 HTTP 服务器,因此,我们可以在另一个 SSH 终端中运行以下命令。
http GET :8888/version
之前的命令将组成并发送以下 HTTP 请求:GET http://localhost:8888/version。该请求是我们 RESTful API 中的最简单情况,因为它将匹配并运行只接收 self 作为参数的 VersionHandler.get 方法,因为 URL 模式不包含任何参数。该方法创建一个响应字典,然后调用 self.write 方法并将 response 作为参数。self.write 方法将接收到的数据块写入输出缓冲区。因为数据块(response)是一个字典,self.write 将其作为 JSON 写入,并将响应的 Content-Type 设置为 application/json。以下行显示了 HTTP 请求的示例响应,包括响应头:
HTTP/1.1 200 OK
Content-Length: 46
Content-Type: application/json; charset=UTF-8
Date: Thu, 28 Jan 2016 03:15:21 GMT
Etag: "fb066668a345b0637fdc112ac0ddc37c318d8709"
Server: TornadoServer/4.3
{
"last_build": "2016-01-28",
"version": "1.0"
}
如果我们不想在响应中包含头信息,我们可以使用 -b 选项执行 HTTPie。例如,以下行执行相同的 HTTP 请求,但不在响应输出中显示头信息。
http –b GET :8888/version
一旦我们知道我们的请求正在正常运行,我们可以打开一个新的终端、命令行或我们想要使用的 GUI 工具,从计算机或任何连接到局域网的设备上编写和发送 HTTP 请求。我们只需要在我们的请求 URL 中使用分配给板的 IP 地址而不是 localhost。不要忘记在下一个请求中将 192.168.1.107 替换为您的板 IP 地址。
现在,我们可以在计算机或设备上运行以下 HTTPie 命令来使用 RESTful API 使板上的五个 LED 点亮。在您输入命令后,您会注意到显示 Python 代码输出的 SSH 终端将显示一条消息,表明它正在点亮 5 个 LED 以及指示正在点亮和关闭的 LED 的附加消息。此外,您将看到 5 个 LED 点亮。
http -b PUT 192.168.1.107:8888/putnumberinleds/5
之前的命令将组成并发送以下 HTTP 请求:PUT http://192.168.1.107:8888/putnumberinleds/5。该请求将匹配并运行接收 5 作为其 number 参数的 PutNumberInLedsHandler.put 方法。以下行显示了 HTTP 服务器的响应,其中包含了打印在 LED 上的数字,即已点亮的 LED 数量:
{
"number": 5
}
以下图像显示了 OS X 上并排的两个终端窗口。左侧的终端窗口在一台生成 HTTP 请求的计算机上运行,右侧的终端窗口是运行在 Yocto Linux 上的 Tornado HTTP 服务器 SSH 终端,它显示了我们 Python 代码的输出。在组合和发送 HTTP 请求时检查输出,使用类似的配置是一个好主意。

在 Fiddler 中,点击 Composer 或按 F9,在 Parsed 选项卡的下拉菜单中选择 PUT,然后在下拉菜单右侧的文本框中输入 192.168.1.107:8888/putnumberinleds/5(不要忘记将 IP 替换为您的板子 IP)。然后,点击 Execute 并双击捕获日志中出现的 200 结果。如果您想查看原始响应,只需点击 Request Headers 面板下方的 Raw 按钮即可。
以下图像显示了 Windows 上 Fiddler 窗口和 Putty 终端窗口并排。左侧的 Fiddler 窗口在一台生成 HTTP 请求的计算机上运行,右侧的 Putty 终端窗口是运行在 Yocto Linux 上的 SSH 终端,它显示了我们 Python 代码的输出。

我们可以在计算机或设备上运行以下 HTTPie 命令来使用 RESTful API 告诉我们已点亮多少个 LED。
http -b GET 192.168.1.107:8888/getcurrentnumber
上一条命令将组合并发送以下 HTTP 请求:GET http://192.168.1.107:8888/getcurrentnumber。该请求将匹配并运行 GetCurrentNumber.get 方法。以下几行显示了来自 HTTP 服务器的响应,其中包含了打印在 LED 上的数字,即最后一次 API 调用中已点亮的 LED 数量:
{
"number": 5
}
如果我们再次查看构成 Web 应用的请求处理器列表,我们会注意到 putnumberinleds 的条目指定了一个正则表达式,该正则表达式接受从 0 到 9 的数字作为其参数:
(r"/putnumberinleds/([0-9])", PutNumberInLedsHandler)
如果我们在计算机或设备上运行以下 HTTPie 命令来使用 RESTful API 使板子点亮十二个 LED,请求将不会匹配请求处理器列表中的任何正则表达式。
http -b PUT 192.168.1.107:8888/putnumberinleds/12
因此,Tornado 将返回一个 404: Not found 错误作为结果。
<html><title>404: Not Found</title><body>404: Not Found</body></html>
如果我们在计算机或设备上运行以下 HTTPie 命令,也会发生相同的情况,因为 x 不是 0 到 9 之间的数字。
http -b PUT 192.168.1.107:8888/putnumberinleds/x
以下 HTTPie 命令将点亮 8 个 LED。
http -b PUT 192.168.1.107:8888/putnumberinleds/8
上一条命令将组合并发送以下 HTTP 请求:PUT http://192.168.1.107:8888/putnumberinleds/8。该请求将匹配并运行接收 8 作为其 number 参数的 PutNumberInLedsHandler.put 方法。以下几行显示了来自 HTTP 服务器的响应,其中包含了打印在 LED 上的数字,即已点亮的 LED 数量:
{
"number": 8
}
打开的 LED 数量从 5 变为 8,因此,我们可以在计算机或设备上运行以下 HTTPie 命令,使用 RESTful API 告诉我们打开了多少个 LED。
http -b GET 192.168.1.107:8888/getcurrentnumber
以下行显示了 HTTP 服务器响应,其中包含已打印在 LED 上的数字:
{
"number": 8
}
我们创建了一个非常简单的 RESTful API,允许我们打开 LED 并检查当前打印在 LED 上的数字。当然,我们应该向 RESTful API 添加身份验证和整体安全性,以使其完整。我们的 RESTful API 使我们能够使用任何可以编写和发送 HTTP 请求的应用程序、移动应用程序或 Web 应用程序在 LED 上打印数字。
带 PWM 功能的引脚布线
我们想要控制输出电压,以便能够淡入淡出三种不同颜色的三个 LED:红色、绿色和蓝色。输出电压越低,LED 的亮度级别越低。输出电压越高,LED 的亮度级别越高。因此,当输出电压接近 0V 时,LED 的亮度较低,而当输出电压接近 IOREF 电压,即在我们的实际配置中为 5V 时,LED 的亮度较高。具体来说,我们希望能够为每个 LED 设置 256 个亮度级别,从 0 到 255。在这种情况下,我们将使用三个 LED,但稍后在本章中我们将转向一个能够在一个电子组件中混合三种颜色的单色 RGB LED。
当我们使用配置为数字输出的 GPIO 引脚工作时,我们可以设置 0V(低值)或 IOREF 电压,即在我们的实际配置中为 5V(高值)。因此,我们可以通过其最大亮度级别关闭或打开 LED(而不烧毁它)。
如果我们将红色、绿色和蓝色 LED 连接到三个 GPIO 引脚,并将它们配置为数字输出,我们将无法设置 256 个亮度级别。我们必须将三个 LED 连接到三个我们可以用作PWM(脉冲宽度调制)输出引脚的数字 I/O 引脚。在第一章中,理解和设置基础物联网硬件,当我们学习了英特尔 Galileo Gen 2 板上的 I/O 引脚时,我们了解到带有波浪线符号(~)作为数字前缀的引脚可以用作 PWM 输出引脚。因此,我们可以使用以下引脚连接三个 LED:
-
将~6引脚连接到红色 LED
-
将~5引脚连接到绿色 LED
-
将~3引脚连接到蓝色 LED
在完成必要的布线后,我们将编写 Python 代码来创建另一个 RESTful API,允许我们设置三个 LED 中的每个 LED 的亮度。我们需要以下部分来使用此示例:
-
一个红色超亮 5mm LED
-
一个绿色超亮 5mm LED
-
一个蓝色超亮 5mm LED
-
三个 5%容差的 270Ω电阻(红紫棕金)
下图显示了连接到面包板的组件、必要的布线和从英特尔 Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件为iot_fritzing_chapter_04_02.fzz,以下图像是面包板视图:

在这种情况下,我们希望三个 LED 彼此靠近。这样,三个 LED 可以将光线投射到黑色表面上,我们可以看到三种颜色的交汇处产生的颜色将类似于我们在稍后将要使用的颜色选择器中选择的颜色。
以下图像显示了用符号表示的电子组件的原理图。

如前图所示,板上的符号中标记为D3 PWM、D5 PWM和D6 PWM的三个具有 PWM 功能的 GPIO 引脚连接到一个270Ω电阻,该电阻连接到 LED 的正极,每个 LED 的负极连接到地。
现在,是时候将组件插入面包板并完成所有必要的布线了。不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并在从板上的引脚添加或移除任何电线之前,从英特尔 Galileo Gen 2 板上拔掉电源。
使用 PWM 生成模拟值
脉宽调制,简称 PWM,是一种通过使用数字开/关模式,通过数字方式生成模拟结果的技术。提供 PWM 功能的引脚使用数字控制来创建方波,并且可以通过控制信号在开启状态(IOREF电压)和关闭状态(0V)中停留的时间来模拟配置的IOREF电压(默认板配置为 5V)和 0V 之间的电压。脉冲宽度是信号在开启状态(IOREF电压)中的持续时间,因此,脉宽调制意味着通过改变脉冲宽度来获取感知到的模拟值。
当你每秒重复数百次开启状态和关闭状态的信号,并且将 LED 连接到 PWM 引脚时,我们可以生成与信号在 0V 和IOREF电压之间保持恒定电压相同的信号,以控制 LED 的亮度级别。
我们可以将从 0 到 1 的浮点值写入配置为模拟输出的 PWM 启用引脚,即从 0%占空比(始终处于关闭状态)到 100%占空比(始终处于开启状态)。我们想要表示 256 个亮度值(从 0 到 255),因此,以下图表显示了横坐标轴(x-轴)上的亮度值和相应的浮点值,这些值必须写入纵坐标轴(y-轴)上的引脚。

之前图表的方程式如下:y = x / 255,具体来说,值 = 亮度 / 255。我们可以在我们的 Python 解释器中运行以下代码来查看所有从 0 到 255(包括)的亮度级别将写入的所有值。
for brightness in range(0, 256):
print(brightness / 255.0)
我们可以将浮点值乘以 5 来计算每个亮度级别的电压值。由于我们使用的是板子的默认设置,IOREF 跳线设置为 5V,因此输出中的 1.0 值表示 5V(1.0 x 5 = 5)。输出中的 0.5 值表示 2.5V(0.5 x 5 = 2.5)。以下图表显示了横坐标轴(x 轴)上的亮度值和对应于纵坐标轴(y 轴)上生成相应亮度值的输出电压值。

之前图表的方程式如下:y = x / 255 * 5,具体来说,电压 = 亮度 / 255 * 5。我们可以在我们的 Python 解释器中运行以下代码来查看所有从 0 到 255(包括)的亮度级别将生成的所有电压输出。
for brightness in range(0, 256):
print(brightness / 255.0 * 5)
我们将创建一个新的 AnalogLed 类来表示连接到我们的板子上的 LED,该 LED 可以具有从 0 到 255(包括)的亮度级别。以下行显示了新 AnalogLed 类的代码。示例的代码文件为 iot_python_chapter_04_02.py。
import mraa
from datetime import date
import tornado.escape
import tornado.ioloop
import tornado.web
class AnalogLed:
def __init__(self, pin, name):
self.pin = pin
self.name = name
self.pwm = mraa.Pwm(pin)
self.pwm.period_us(700)
self.pwm.enable(True)
self.brightness_value = 0
self.set_bightness(0)
def set_brightness(self, value):
brightness_value = value
if brightness_value > 255:
brightness_value = 255
elif brightness_value < 0:
brightness_value = 0
led_value = brightness_value / 255.0
self.pwm.write(led_value)
self.brightness_value = brightness_value
print("{0} LED connected to PWM Pin #{1} set to brightness {2}.".format(self.name, self.pin, brightness_value))
当我们在 pin 必需参数中创建 AnalogLed 类的实例时,我们必须指定连接 LED 的引脚号,并在 name 必需参数中指定 LED 的名称。构造函数,即 __init__ 方法,创建一个新的 mraa.Pwm 实例,其 pin 参数为接收到的 pin,将其引用保存到 pwm 属性中,并调用其 period_us 方法以将 PWM 周期配置为 700 微秒(700 µs)。因此,输出占空比将确定信号处于 ON 状态的 700 微秒周期中的百分比。例如,0.5(50%)的输出占空比意味着信号将在 700 微秒周期中的 350 微秒内处于 ON 状态(700 * 0.5 = 350)。
然后,代码使用 True 作为参数调用 pwm.enable 方法,以设置 PWM 引脚的启用状态,并允许我们通过调用 pwm.write 方法开始设置 PWM 引脚的输出占空比百分比。
下一行创建了一个 brightness_value 属性,其初始值为 0,这将使我们能够轻松检索设置到连接到引脚的 LED 的最后一个亮度值。最后,构造函数使用 0 作为 value 参数的值调用 set_brightness 方法,以将配置引脚连接的 LED 的亮度级别设置为 0。
该类定义了一个set_brightness方法,该方法接收一个亮度级别值作为value参数。代码的前几行确保我们始终设置一个介于 0 到 255(包括)之间的亮度级别。如果value参数的值不包含在该范围内,代码将较低级别(0)或较高级别(255)的值分配给brightness_value变量。
然后,代码计算 PWM 引脚所需的输出占空比百分比,以表示亮度级别作为一个介于1.0f(100%)和0.0f(0%)之间的浮点值。代码将此值保存在led_value变量中,然后使用此变量作为百分比参数调用self.pwm.write方法,以设置配置为 PWM 输出的引脚的输出占空比为led_value。下一行将有效的亮度级别保存到brightness_value属性中。
最后,代码打印有关 LED 名称、引脚编号和已设置的亮度级别的详细信息。这样,该方法将亮度级别从 0 到 255(包括)转换为适当的输出占空比值,并将输出写入以控制连接的 LED 的亮度级别。
现在,我们可以编写使用新的AnalogLed类创建每个 LED 的一个实例的代码,并轻松控制它们的亮度级别。以下行显示了BoardInteraction类的代码。示例的代码文件是iot_python_chapter_04_02.py。
class BoardInteraction:
# The Red LED is connected to pin ~6
red_led = AnalogLed(6, 'Red')
# The Green LED is connected to Pin ~5
green_led = AnalogLed(5, 'Green')
# The Blue LED is connected to Pin ~3
blue_led = AnalogLed(3, 'Blue')
BoardInteraction类仅声明了三个类属性:red_led、green_led和blue_led。这三个类属性保存了之前创建的AnalogLed类的新实例,并代表连接到引脚6**、**5和~3的红、绿和蓝 LED。现在,我们将创建其他类,这些类定义了与这些类属性一起工作的方法,以访问常见的AnalogLed实例。
下一行显示了添加以下类的代码:VersionHandler、PutRedBrightnessHandler、PutGreenBrightnessHandler和PutBlueBrightnessHandler。示例的代码文件是iot_python_chapter_04_02.py。
class VersionHandler(tornado.web.RequestHandler):
def get(self):
response = {'version': '1.0',
'last_build': date.today().isoformat()}
self.write(response)
class PutRedBrightnessHandler(tornado.web.RequestHandler):
def put(self, value):
int_value = int(value)
BoardInteraction.red_led.set_brightness(int_value)
response = {'red': BoardInteraction.red_led.brightness_value}
self.write(response)
class PutGreenBrightnessHandler(tornado.web.RequestHandler):
def put(self, value):
int_value = int(value)
BoardInteraction.green_led.set_brightness(int_value)
response = {'green': BoardInteraction.green_led.brightness_value}
self.write(response)
class PutBlueBrightnessHandler(tornado.web.RequestHandler):
def put(self, value):
int_value = int(value)
BoardInteraction.blue_led.set_brightness(int_value)
response = {'blue': BoardInteraction.blue_led.brightness_value}
self.write(response)
代码声明了以下四个tornado.web.RequestHandler的子类:
-
VersionHandler:定义了一个无参数的get方法,该方法返回包含版本号和最后构建日期的响应。 -
PutRedBrightnessHandler:定义了一个put方法,该方法需要一个value参数,该参数指定了红色 LED 所需的亮度级别。该方法调用存储在BoardInteraction.red_led类属性中的AnalogNumber实例的set_brightness方法,并使用value参数中指定的所需亮度级别。然后,代码返回一个响应,其中包含已转换为 PWM 引脚输出占空比百分比的亮度级别,该 PWM 引脚连接到红色 LED。 -
PutGreenBrightnessHandler: 定义了put方法来设置绿色 LED 所需的亮度级别。它的工作方式与之前描述的PutRedBrightnessHandler方法相同,但代码使用BoardInteraction.green_led类属性来控制绿色 LED 的亮度级别,而不是使用BoardInteraction.red_led类属性。 -
PutBlueBrightnessHandler: 定义了put方法来设置蓝色 LED 所需的亮度级别。它的工作方式与之前描述的PutRedBrightnessHandler方法相同,但代码使用BoardInteraction.blue_led类属性来控制蓝色 LED 的亮度级别,而不是使用BoardInteraction.red_led类属性。
下面的几行展示了添加以下类的代码:GetRedBrightnessHandler、GetGreenBrightnessHandler 和 GetBlueBrightnessHandler。示例的代码文件是 iot_python_chapter_04_02.py。
class GetRedBrightnessHandler(tornado.web.RequestHandler):
def get(self):
response = {'red': BoardInteraction.red_led.brightness_value}
self.write(response)
class GetGreenBrightnessHandler(tornado.web.RequestHandler):
def get(self):
response = {'green': BoardInteraction.green_led.brightness_value}
self.write(response)
class GetBlueBrightnessHandler(tornado.web.RequestHandler):
def get(self):
response = {'blue': BoardInteraction.blue_led.brightness_value}
self.write(response)
代码声明了以下三个 tornado.web.RequestHandler 的子类:
-
GetRedBrightnessHandler: 定义了一个无参数的get方法,该方法返回一个包含BoardInteraction.red_led.brightness_value属性值的响应,即设置到红色 LED 的亮度值 -
GetGREENBrightnessHandler: 定义了一个无参数的get方法,该方法返回一个包含BoardInteraction.green_led.brightness_value属性值的响应,即设置到绿色 LED 的亮度值 -
GetBlueBrightnessHandler: 定义了一个无参数的get方法,该方法返回一个包含BoardInteraction.blue_led.brightness_value属性值的响应,即设置到蓝色 LED 的亮度值
下面的几行使用之前声明的 tornado.web.RequestHandler 子类,用 Tornado 构建了一个 Web 应用程序,它代表一个新的 RESTful API 和新的 __main__ 方法。示例的代码文件是 iot_python_chapter_04_02.py。
application = tornado.web.Application([
(r"/putredbrightness/([0-9]+)", PutRedBrightnessHandler),
(r"/putgreenbrightness/([0-9]+)", PutGreenBrightnessHandler),
(r"/putbluebrightness/([0-9]+)", PutBlueBrightnessHandler),
(r"/getredbrightness", GetRedBrightnessHandler),
(r"/getgreenbrightness", GetGreenBrightnessHandler),
(r"/getbluebrightness", GetBlueBrightnessHandler),
(r"/version", VersionHandler)])
if __name__ == "__main__":
print("Listening at port 8888")
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
如前例所示,代码创建了一个名为 application 的 tornado.web.Application 类实例,其中包含构成 Web 应用的请求处理器列表,即正则表达式和 tornado.web.RequestHandler 子类的元组。
下表显示了与前面代码中定义的正则表达式匹配的一些 HTTP 请求。在这种情况下,HTTP 请求使用 192.168.1.107,因为它们是从连接到我们局域网的计算机执行的。不要忘记在下一个请求中将 192.168.1.107 替换为您的板子的 IP 地址。
| HTTP 方法及请求 URL | 匹配请求路径的元组 (regexp, request_class) |
被调用的 RequestHandler 子类和方法 |
|---|---|---|
PUT http:// 192.168.1.107:8888/putredbrightness/30 |
(r"/putredbrightness/([0-9]+)", PutRedBrightnessHandler) |
PutRedBrightnessHandler.put(30) |
PUT http:// 192.168.1.107:8888/putgreenbrightness/128 |
(r"/putgreenbrightness/([0-9]+)", PutGreenBrightnessHandler) |
PutGreenBrightnessHandler.put(128) |
PUT http:// 192.168.1.107:8888/putbluebrightness/255 |
(r"/putbluebrightness/([0-9]+)", PutBlueBrightnessHandler) |
PutGreenBrightnessHandler.put(255) |
GET http:// 192.168.1.107:8888/getredbrightness |
(r"/getredbrightness", GetRedBrightnessHandler) |
GetRedBrightnessHandler.get() |
GET http:// 192.168.1.107:8888/getgreenbrightness |
(r"/getgreenbrightness", GetGreenBrightnessHandler) |
GetGreenBrightnessHandler.get() |
GET http:// 192.168.1.107:8888/getbluebrightness |
(r"/getbluebrightness", GetBlueBrightnessHandler) |
GetBlueBrightnessHandler.get() |
以下行将启动 HTTP 服务器和我们的 RESTful API,允许我们在板上的 Yocto Linux 中控制红色、绿色和蓝色 LED 的亮度级别。不要忘记,你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux,正如前一章所述。
python iot_python_chapter_04_02.py
启动 HTTP 服务器后,我们将看到以下输出,并且所有红色、绿色和蓝色 LED 都将被关闭。
Red LED connected to PWM Pin #6 set to brightness 0.
Green LED connected to PWM Pin #5 set to brightness 0.
Blue LED connected to PWM Pin #3 set to brightness 0.
Listening at port 8888
通过 HTTP 请求生成模拟值
HTTP 服务器正在 Yocto Linux 中运行,等待我们的 HTTP 请求来控制连接到 Intel Galileo Gen 2 板的 LED。现在,我们将从连接到我们的局域网的另一台计算机或设备上编写和发送 HTTP 请求,并将控制红色、绿色和蓝色 LED 的亮度级别。
打开一个新的终端、命令行或我们想要用来从计算机或任何连接到局域网的设备上编写和发送 HTTP 请求的 GUI 工具。不要忘记在接下来的请求中将 192.168.1.107 替换为你的板子的 IP 地址。
在计算机或设备上运行以下 HTTPie 命令来使用 RESTful API 将板子的红色 LED 亮度设置为 30。输入命令后,你会注意到显示 Python 代码输出的 SSH 终端将显示以下消息:连接到 PWM 引脚 #6 的红色 LED 设置为亮度 30。此外,你将看到红色 LED 以非常低的亮度打开。
http -b PUT 192.168.1.107:8888/putredbrightness/30
之前的命令将编写并发送以下 HTTP 请求:PUT http://192.168.1.107:8888/putredbrightness/30。请求将匹配并运行接收 30 作为其 value 参数的 PutRedBrightnessHandler.put 方法。以下行显示了 HTTP 服务器对通过 PWM 设置的红色 LED 亮度级别的响应:
{
"red": 30
}
我们可以在计算机或设备上运行以下 HTTPie 命令来使用 RESTful API 获取红色 LED 的当前亮度级别。
http -b GET 192.168.1.107:8888/getredbrightness
之前的命令将组合并发送以下 HTTP 请求:GET http://192.168.1.107:8888/getredbrightness。该请求将匹配并运行 GetRedBrightnessHandler.get 方法。以下几行显示了 HTTP 服务器对之前通过 API 调用设置的红色 LED 亮度级别的响应:
{
"red": 30
}
现在,在计算机或设备上运行以下 HTTPie 命令以使用 RESTful API 将绿色 LED 的亮度设置为 128。在您输入命令后,您将注意到显示 Python 代码输出的 SSH 终端将显示以下消息:连接到 PWM 引脚 #5 的绿色 LED 设置为亮度 128。此外,您将看到绿色 LED 以非常低的亮度级别打开。
http -b PUT 192.168.1.107:8888/putredbrightness/128
之前的命令将组合并发送以下 HTTP 请求:PUT http://192.168.1.107:8888/putgreenbrightness/128。该请求将匹配并运行接收其 value 参数中的 128 的 PutGreenBrightnessHandler.put 方法。以下几行显示了 HTTP 服务器对已设置的绿色 LED 亮度级别的响应:
{
"green": 128
}
最后,我们在计算机或设备上运行以下 HTTPie 命令以使用 RESTful API 将板上的蓝色 LED 亮度设置为 255,即其最高亮度级别。在您输入命令后,您将注意到显示 Python 代码输出的 SSH 终端将显示以下消息:连接到 PWM 引脚 #3 的蓝色 LED 设置为亮度 255。此外,您将看到蓝色 LED 以其最高亮度级别打开。
http -b PUT 192.168.1.107:8888/putbluebrightness/255
之前的命令将组合并发送以下 HTTP 请求:PUT http://192.168.1.107:8888/putbluebrightness/255。该请求将匹配并运行接收其 value 参数中的 255 的 PutBlueBrightnessHandler.put 方法。以下几行显示了 HTTP 服务器对已设置的蓝色 LED 亮度级别的响应:
{
"blue": 255
}
现在,我们可以运行以下两个 HTTPie 命令来使用 RESTful API 告诉我们绿色和蓝色 LED 的当前亮度级别。
http -b GET 192.168.1.107:8888/getgreenbrightness
http -b GET 192.168.1.107:8888/getbluebrightness
以下几行显示了 HTTP 服务器对已设置的绿色和蓝色 LED 亮度级别的两个响应:
{
"green": 128
}
{
"blue": 255
}
我们创建了一个非常简单的 RESTful API,允许我们设置红色、绿色和蓝色 LED 的所需亮度,并检查它们的当前亮度级别。我们的 RESTful API 使得我们能够通过三种颜色及其不同亮度级别的交集,在任何可以组合和发送 HTTP 请求的应用程序、移动应用程序或 Web 应用程序中生成不同的颜色。
准备 RESTful API 以满足 Web 应用程序需求
我们希望开发一个简单的 Web 应用程序,显示一个颜色选择器,允许用户选择颜色。一旦用户选择了一种颜色,我们就可以从 0 到 255 获取红色、绿色和蓝色的分量。我们希望根据所选颜色的红色、绿色和蓝色值设置板上红色、绿色和蓝色 LED 的亮度级别。根据这一要求,添加一个新的PUT方法到我们的 RESTful API 中,以便我们可以在单个 API 调用中更改三个 LED 的亮度级别是方便的。
下面的行显示了添加新PutRGBBrightnessHandler类的代码。示例的代码文件是iot_python_chapter_04_03.py。
class PutRGBBrightnessHandler(tornado.web.RequestHandler):
def put(self, red, green, blue):
int_red = int(red)
int_green = int(green)
int_blue = int(blue)
BoardInteraction.red_led.set_brightness(int_red)
BoardInteraction.green_led.set_brightness(int_green)
BoardInteraction.blue_led.set_brightness(int_blue)
response = dict(
red=BoardInteraction.red_led.brightness_value,
green=BoardInteraction.green_led.brightness_value,
blue=BoardInteraction.blue_led.brightness_value)
self.write(response)
代码声明了一个名为PutRGBBrightnessHandler的新子类,该子类名为tornado.web.RequestHandler。该类定义了一个put方法,该方法需要三个参数,用于指定三个 LED(红色、绿色和蓝色)所需的亮度。该方法调用存储在BoardInteraction.red_led、BoardInteraction.green_led和BoardInteraction.blue_led类属性中的AnalogNumber实例的set_brightness方法,并使用参数中指定的所需亮度级别。然后,代码返回一个响应,其中亮度级别已转换为连接到红色、绿色和蓝色 LED 的 PWM 引脚的输出占空比。
现在,有必要将突出显示的行添加到创建名为application的tornado.web.Application类实例的代码中,该实例具有构成 Web 应用程序的请求处理程序列表,即正则表达式和tornado.web.RequestHandler子类的元组。示例的代码文件是iot_python_chapter_04_03.py。
application = tornado.web.Application([
(r"/putredbrightness/([0-9]+)", PutRedBrightnessHandler),
(r"/putgreenbrightness/([0-9]+)", PutGreenBrightnessHandler),
(r"/putbluebrightness/([0-9]+)", PutBlueBrightnessHandler),
(r"/putrgbbrightness/r([0-9]+)g([0-9]+)b([0-9]+)",
PutRGBBrightnessHandler),
(r"/getredbrightness", GetRedBrightnessHandler),
(r"/getgreenbrightness", GetGreenBrightnessHandler),
(r"/getbluebrightness", GetBlueBrightnessHandler),
(r"/version", VersionHandler)])
以下行将启动 HTTP 服务器和我们在板上运行的 Yocto Linux 的新版 RESTful API,该 API 允许我们通过单个 API 调用控制红色、绿色和蓝色 LED 的亮度级别。不要忘记,你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux,正如前一章所述。
python iot_python_chapter_04_03.py
在我们启动 HTTP 服务器后,我们将看到以下输出,并且所有红色、绿色和蓝色 LED 都将关闭。
Red LED connected to PWM Pin #6 set to brightness 0.
Green LED connected to PWM Pin #5 set to brightness 0.
Blue LED connected to PWM Pin #3 set to brightness 0.
Listening at port 8888
使用新的 RESTful API,我们可以组合以下 HTTP 动词和请求 URL:
PUT http://192.168.1.107:8888/putrgbbrightness/r30g128b255
之前请求的路径将与之前添加的元组(regexp,request_class)(r"/putrgbbrightness/r([0-9]+)g([0-9]+)b([0-9]+)", PutRGBBrightnessHandler))匹配,Tornado 将调用PutRGBBrightnessHandler.put方法,并带有红色、绿色和蓝色的值,具体为PutRGBBrightnessHandler.put(30, 128, 255)`。
在计算机或设备上运行以下 HTTPie 命令以使用 RESTful API 通过之前分析的请求路径设置板上三个 LED 的亮度级别。
http -b PUT 192.168.1.107:8888/putrgbbrightness/r30g128b255
在你输入命令后,你会注意到 SSH 终端会显示 Python 代码的输出,将显示以下三条消息:
-
红色 LED 连接到 PWM 引脚#6,亮度设置为 30
-
绿色 LED 连接到 PWM 引脚#5,亮度设置为 128
-
蓝色 LED 连接到 PWM 引脚#3,亮度设置为 255
此外,您将看到三个 LED 以不同的亮度级别点亮。以下行显示了 HTTP 服务器对已设置三个 LED 亮度级别的响应:
{
"blue": 255,
"green": 128,
"red": 30
}
使用 PWM 和 RESTful API 设置 RGB LED 的颜色
现在,我们将使用相同的源代码来改变 RGB LED 的颜色,特别是共阴极 RGB LED。这个电子元件提供了一个共阴极和三个阳极,即每个颜色(红、绿、蓝)都有一个阳极。我们可以使用我们的代码来脉冲宽度调制三种颜色,使 LED 产生混合颜色。我们不需要使用黑色表面来观察三种颜色的交汇,因为 RGB LED 会为我们混合这三种颜色。
下图展示了一个常见的共阴极 RGB LED 及其引脚的配置,其中共阴极是第二个引脚,也是最长的引脚。

下表显示了之前 RGB LED 的引脚配置,从左到右。然而,始终确保检查您 RGB LED 的数据表,以确认共阴极和每种颜色的阳极的正确引脚。
| 引脚编号 | 描述 |
|---|---|
| 1 | 红色 LED 的阳极引脚 |
| 2 | 共阴极引脚 |
| 3 | 绿色 LED 的阳极引脚 |
| 4 | 蓝色 LED 的阳极引脚 |
根据之前的表格,我们将连接三个阳极引脚到三个我们可以用作PWM(脉冲宽度调制)输出引脚的数字 I/O 引脚。我们将使用与之前示例中相同的 PWM 输出引脚:
-
引脚~6用于连接红色 LED 的阳极引脚
-
引脚~5用于连接绿色 LED 的阳极引脚
-
引脚~3用于连接蓝色 LED 的阳极引脚。
完成必要的接线后,我们将使用相同的 Python 代码运行我们的 RESTful API,并通过改变红、绿、蓝三色的亮度级别来混合颜色。我们需要以下部件来完成此示例:
-
一个常见的共阴极 5mm RGB LED
-
三个 270Ω电阻,公差为 5%(红紫棕金)
下图显示了连接到面包板的组件、必要的接线以及从英特尔 Galileo Gen 2 板到面包板的接线。该示例的 Fritzing 文件为iot_python_chapter_04_03.fzz,以下图像是面包板视图:

下图展示了带有电子元件符号的电路图:

如前图所示,板上的符号中标记为 D3 PWM、D5 PWM 和 D6 PWM 的三个具有 PWM 功能的 GPIO 引脚连接到一个 270Ω 电阻,该电阻连接到每个 LED 颜色的阳极引脚,而公共阴极连接到地。
现在,是时候将组件插入到面包板中并完成所有必要的布线了。不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并在从板上的引脚添加或移除任何电线之前,从英特尔 Galileo Gen 2 板上拔掉电源。
在板载 Yocto Linux 启动后,我们必须启动我们的最新版本的 RESTful API 的 HTTP 服务器,该 API 允许我们通过单个 API 调用来控制红、绿和蓝 LED 的亮度级别。
python iot_python_chapter_04_03.py
在计算机或设备上运行以下 HTTPie 命令以使用 RESTful API 使板设置 RGB LED 中包含的颜色亮度级别。
http -b PUT 192.168.1.107:8888/putrgbbrightness/r255g255b0
在您输入命令后,您会注意到 RGB LED 显示出黄色光,因为我们同时将红色和绿色设置为最大亮度级别,同时关闭了蓝色组件。以下几行显示了 HTTP 服务器对三种颜色已设置的亮度级别的响应:
{
"blue": 0,
"green": 255,
"red": 255
}
现在,运行以下 HTTPie 命令。
http -b PUT 192.168.1.107:8888/putrgbbrightness/r255g0b128
在您输入命令后,您会注意到 RGB LED 显示出粉红色或浅紫色光,因为我们设置了绿色为最大亮度级别,蓝色为最大亮度的一半,同时关闭了绿色组件。以下几行显示了 HTTP 服务器对三种颜色已设置的亮度级别的响应:
{
"blue": 128,
"green": 0,
"red": 255
}
现在,运行以下 HTTPie 命令:
http -b PUT 192.168.1.107:8888/putrgbbrightness/r0g255b255
在您输入命令后,您会注意到 RGB LED 显示出青色光,因为我们同时将绿色和蓝色设置为最大亮度级别,同时关闭了红色组件。以下几行显示了 HTTP 服务器对三种颜色已设置的亮度级别的响应:
{
"blue": 255,
"green": 255,
"red": 0
}
我们可以生成 256 * 256 * 256 种不同的颜色,即 16,777,216 种颜色(超过 1600 万种颜色),这是 RGB LED 产生的光的颜色。我们只需使用我们的 RESTful API 并更改红、绿和蓝组件的值即可。
使用 wiring-x86 库控制 PWM
到目前为止,我们一直在使用 mraa 库来处理 PWM 并更改 RGB LED 中不同 LED 和颜色的亮度级别。然而,在第一章中,我们也安装了 wiring-x86 库。我们可以通过更改几行面向对象的代码来用 wiring-x86 库替换 mraa 库,以更改红、绿和蓝组件的亮度级别。
当使用 PWM 时,mraa库和wiring-x86库之间存在一个重要的区别。前者使用 0.0f 到 1.0f 的浮点值来设置输出占空比百分比,但后者使用 0 到 255(包含)的值来设置此值。因此,当使用wiring-x86库时,我们不需要将所需的亮度级别转换为输出占空比百分比,我们可以使用亮度级别值来指定 PWM 的值。因此,在这种情况下,代码更简单。
以下行显示了Board类的代码,随后是使用wiring-x86库而不是使用mraa的AnalogLed类的新版本。示例的代码文件为iot_python_chapter_04_04.py。
from wiringx86 import GPIOGalileoGen2 as GPIO
class Board:
gpio = GPIO(debug=False)
class AnalogLed:
def __init__(self, pin, name):
self.pin = pin
self.name = name
self.gpio = Board.gpio
self.gpio.pinMode(pin, self.gpio.PWM)
self.brightness_value = 0
self.set_brightness(0)
def set_brightness(self, value):
brightness_value = value
if brightness_value > 255:
brightness_value = 255
elif brightness_value < 0:
brightness_value = 0
self.gpio.analogWrite(self.pin, brightness_value)
self.brightness_value = brightness_value
print("{0} LED connected to PWM Pin #{1} set to brightness {2}.".format(self.name, self.pin, brightness_value))
我们只需要从AnalogLed类的上一个代码中更改几行。与wiring-x86库交互的新行在之前的代码中突出显示。构造函数,即__init__方法,将Board.gpio类属性引用保存到self.gpio中,并使用接收到的pin作为其pin参数,使用self.gpio.PWM作为其mode参数调用其pinMode方法。这样,我们配置引脚为输出 PWM 引脚。所有的Led实例都将保存对创建GPIO类实例的同一Board.gpio类属性的引用,特别是具有debug参数设置为False的wiringx86.GPIOGalileoGen2类,以避免低级通信中的不必要调试信息。
set_brightness方法调用 GPIO 实例(self.gpio)的analogWrite方法来设置配置为 PWM 输出的引脚的输出占空比为brightness_value。self.pin属性指定了analogWrite方法调用中的pin值。因为brightness_value已经是一个介于 0 和 255(包含)之间的值,所以它是analogWrite方法的合法值。
我们 RESTful API 的其余代码与之前示例中使用的相同。没有必要更改此类,因为它将自动与新的AnalogLed类一起工作,并且其构造函数或set_brightness方法的参数没有发生变化。
以下行将启动 HTTP 服务器和与wiring-x86库一起工作的我们新的 RESTful API 版本。不要忘记,你需要像前一章中解释的那样,使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。
python iot_python_chapter_04_04.py
小贴士
我们可以使用我们在上一个示例中使用的相同 HTTP 请求来检查我们是否可以使用wiring-x86库达到完全相同的结果。
测试你的知识
-
PWM 代表:
-
引脚工作模式。
-
脉冲宽度调制。
-
脉冲宽度调制。
-
-
在 Intel Galileo Gen 2 板上,以下符号作为前缀的引脚可以用作 PWM 输出引脚:
-
哈希符号(#)。
-
美元符号($)。
-
波浪符号(~)。
-
-
PWM 引脚上的 100%占空比(始终处于开启状态)将产生一个稳定的电压,等于:
-
0 V。
-
指定在 IOREF 跳线所在位置的电压。
-
6 V。
-
-
PWM 引脚上的 0%占空比(始终处于关闭状态)将产生一个稳定的电压,等于:
-
0 V。
-
指定在 IOREF 跳线所在位置的电压。
-
6 V。
-
-
在 PWM 引脚上有一个 LED 连接时,50%的占空比将产生与稳定电压等于的结果:
-
0 V。
-
指定在 IOREF 跳线所在位置的电压的一半。
-
6 V * 0.5 = 3 V。
-
摘要
在本章中,我们使用了 Tornado 网络服务器、Python、HTTPie 命令行 HTTP 客户端以及mraa和wiring-x86库。与前面的章节一样,我们利用了 Python 的面向对象特性,并生成了许多版本的 RESTful API,使我们能够与连接到 LAN 的计算机和设备上的板进行交互。
我们可以编写并发送 HTTP 请求,这些请求会在 LED 上打印数字,改变三个 LED 的亮度级别,并使用 RGB LED 生成数百万种颜色。
现在我们已经创建了第一个 RESTful API,这使得计算机和设备能够与我们的物联网设备交互,我们可以利用额外的功能来读取数字输入和模拟值,这是下一章的主题。
第五章:处理数字输入、轮询和中断
在本章中,我们将使用数字输入,以便在处理 HTTP 请求的同时让用户与板交互。我们将:
-
理解上拉电阻和下拉电阻之间的区别,以便连接按钮
-
将按钮与数字输入引脚连接
-
使用轮询检查
mraa和wiring-x86库中的按钮状态 -
在运行 RESTful API 的同时结合轮询读取数字输入
-
编写代码,确保在提供电子组件和 API 的共享功能时保持一致性
-
使用中断和
mraa库检测按下的按钮 -
理解轮询和中断在检测数字输入变化之间的差异、优势和权衡
理解按钮和上拉电阻
我们使用 RESTful API 控制红、绿、蓝 LED 的亮度级别。然后,我们将三个 LED 替换为单个 RGB LED,并使用相同的 RESTful API 生成不同颜色的灯光。现在,我们希望用户能够通过面包板上添加的两个按钮来改变三个组件的亮度级别:
-
一个按钮用于关闭所有颜色,即设置所有颜色亮度级别为 0
-
一个按钮用于将所有颜色设置为最大亮度级别,即设置所有颜色亮度级别为 255
当用户按下按钮,也称为微动开关时,它就像一根电线,因此,它允许电流通过其融入的电路。当按钮未按下时,其融入的电路被中断。因此,每当用户释放按钮时,电路都会被中断。显然,我们不希望在用户按下按钮时短路连接,因此,我们将分析不同的可能方法来安全地将按钮连接到英特尔 Galileo Gen 2 板上。
以下图片显示了我们可以将按钮连接到英特尔 Galileo Gen 2 板的一种方法,并使用 GPIO 引脚号0作为输入以确定按钮是否被按下。该示例的 Fritzing 文件为iot_fritzing_chapter_05_01.fzz,以下图片是面包板视图:

以下图片显示了用符号表示的电子组件的电路图:

如前图所示,板子符号上标记为D0/RX的 GPIO 引脚连接到一个 120Ω电阻,公差为 5%(棕色红棕色金色),并连接到IOREF引脚。我们已经知道,标记为IOREF的引脚为我们提供 IOREF 电压,即在我们的实际配置中为 5V。由于我们可能希望在将来使用其他电压配置,我们始终可以使用 IOREF 引脚而不是专门使用5V或3V3引脚。板子符号上标记为D0/RX的 GPIO 引脚也连接到S1按钮,通过 120Ω电阻和GND(地)连接。
小贴士
该配置被称为分压器,120Ω电阻被称为上拉电阻。
拉高电阻在按下S1按钮时限制电流。由于拉高电阻的作用,如果我们按下S1按钮,我们将在标记为D0/RX的 GPIO 引脚上读取低值(0V)。当我们释放 S1 按钮时,我们将读取高值,即 IOREF 电压(在我们的实际配置中为 5V)。
由于我们在按钮按下时读取到低值,所以情况可能有些令人困惑。然而,我们可以编写面向对象的代码来封装按钮的行为,并使用更容易理解的状态来隔离上拉电阻的工作方式。
还可以使用下拉电阻。我们可以将 120Ω电阻连接到地,将其从上拉电阻转换为下拉电阻。以下图片显示了如何使用下拉电阻将按钮连接到英特尔 Galileo Gen 2 板,并使用 GPIO 引脚号0作为输入来确定按钮是否被按下。该示例的 Fritzing 文件为iot_fritzing_chapter_05_02.fzz,以下图片是面包板视图:

下图显示了用符号表示的电子元件的原理图:

如前图所示,在本例中,板子符号上标记为D0/RX的 GPIO 引脚连接到S1按钮和IOREF引脚。S1 按钮的另一个引脚连接到 120Ω电阻,该电阻连接到GND(地)。
小贴士
在此配置中,120Ω电阻被称为下拉电阻。
拉低电阻在按下S1按钮时限制电流。由于拉低电阻的作用,如果我们按下S1按钮,我们将在标记为D0/RX的 GPIO 引脚上读取高值,即 IOREF 电压(在我们的实际配置中为 5V)。当我们释放S1按钮时,我们将读取低值(0V)。因此,拉低电阻与我们在使用上拉电阻时读取的相反值一起工作。
使用按钮连接数字输入引脚
现在,我们将使用以下引脚连接两个按钮,并将使用上拉电阻:
-
引脚 1(标记为 D1/TX)用于连接关闭三种颜色的按钮
-
引脚 0(标记为 D0/RX)用于连接设置三种颜色到最大亮度级别的按钮
在完成必要的布线后,我们将编写 Python 代码来检查每个按钮是否被按下,同时保持我们的 RESTful API 正常工作。这样,我们将使用户能够通过按钮和 RESTful API 与 RGB LED 交互。为了使用此示例,我们需要以下额外的组件:
-
两个带两个引脚的按钮
-
两个 120Ω 电阻,公差为 5%(棕色 红色 棕色 金色)
以下图显示了连接到面包板上的组件、必要的布线和从英特尔 Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件是 iot_fritzing_chapter_05_03.fzz,以下图片是面包板视图:

以下图片显示了用符号表示的电子组件的原理图。

如前图所示,我们添加了两个按钮(S1 和 S2)和两个 120Ω 上拉电阻(R4 和 R5)。板符号中标记为 D0/RX 的 GPIO 引脚连接到 S2 按钮,R4 电阻是其上拉电阻。板符号中标记为 D1/TX 的 GPIO 引脚连接到 S1 按钮,R5 电阻是其上拉电阻。这样,当 S2 按钮被按下时,GPIO 引脚编号 0 将为低电平,当 S1 按钮被按下时,GPIO 引脚编号 1 将为低电平。S1 按钮位于面包板的左侧,而 S2 按钮位于右侧。
现在,是时候将组件插入面包板并完成所有必要的布线了。在添加或从板上的引脚移除任何电线之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从英特尔 Galileo Gen 2 板上拔掉电源。
使用数字输入和 mraa 库读取按钮状态
我们将创建一个新的 PushButton 类来表示连接到我们的板上的按钮,该按钮可以使用上拉或下拉电阻。以下行显示了与 mraa 库一起工作的新 PushButton 类的代码。示例的代码文件是 iot_python_chapter_05_01.py。
import mraa
import time
from datetime import date
class PushButton:
def __init__(self, pin, pull_up=True):
self.pin = pin
self.pull_up = pull_up
self.gpio = mraa.Gpio(pin)
self.gpio.dir(mraa.DIR_IN)
@property
def is_pressed(self):
push_button_status = self.gpio.read()
if self.pull_up:
# Pull-up resistor connected
return push_button_status == 0
else:
# Pull-down resistor connected
return push_button_status == 1
@property
def is_released(self):
return not self.is_pressed
在创建PushButton类的实例时,我们必须指定按钮连接的引脚号,作为pin必需参数。如果我们没有指定其他值,可选的pull_up参数将为True,实例将像按钮连接了上拉电阻一样工作。如果我们使用下拉电阻,我们必须在pull_up参数中传递False。构造函数,即__init__方法,使用接收到的pin作为其pin参数创建一个新的mraa.Gpio实例,将其引用保存到gpio属性中,并调用其dir方法将引脚配置为输入引脚(mraa.DIR_IN)。
该类定义了以下两个属性:
-
is_pressed:调用相关mraa.Gpio实例的read方法从引脚获取值并将其保存到push_button_status变量中。如果按钮连接了上拉电阻(self.pull_up为True),则代码将返回True,表示如果push_button_status中的值为0(低值),则按钮被按下。如果按钮连接了下拉电阻(self.pull_up为False),则代码将返回True,表示如果push_button_status中的值为1(高值),则按钮被按下。 -
is_released:返回is_pressed属性的相反结果。
现在,我们可以编写使用新的PushButton类创建每个按钮实例的代码,并轻松检查它们是否被按下。新类处理按钮是否连接了上拉或下拉电阻,因此我们只需检查is_pressed或is_released属性值,无需担心它们连接的具体细节。
我们将在稍后集成考虑两个按钮状态的代码到我们的 RESTful API 中。首先,我们将通过一个简单的示例将两个按钮隔离出来,以了解我们如何读取它们的状态。在这种情况下,我们将使用轮询,即一个循环,将检查按钮是否被按下。如果按钮被按下,我们希望代码在控制台输出中打印一条消息,指示正在按下的特定按钮。
以下行显示了执行先前解释操作的 Python 代码。示例的代码文件为iot_python_chapter_05_01.py。
if __name__ == "__main__":
s1_push_button = PushButton(1)
s2_push_button = PushButton(0)
while True:
# Check whether the S1 pushbutton is pressed
if s1_push_button.is_pressed:
print("You are pressing S1.")
# Check whether the S2 pushbutton is pressed
if s2_push_button.is_pressed:
print("You are pressing S2.")
# Sleep 500 milliseconds (0.5 seconds)
time.sleep(0.5)
前两行创建了之前编写的PushButton类的两个实例。S1按钮连接到 GPIO 引脚 1,S2按钮连接到 GPIO 引脚 0。在这两种情况下,代码没有为pull_up参数指定值。因此,构造函数,即__init__方法,将使用此参数的默认值True,并将实例配置为与上拉电阻连接的按钮。我们需要在创建两个实例时注意这一点,然后,我们使用包含实例的变量的名称:s1_push_button和s2_push_button。
然后,代码将无限循环运行,即直到你通过按下Ctrl + C或停止进程的按钮来中断执行,如果你使用具有远程开发功能的 Python IDE 来运行代码在你的板上。
while循环内的第一行检查名为s1_push_button的PushButton实例的is_pressed属性的值是否为True。True值表示此时按钮被按下,因此代码会在控制台输出中打印一条消息,表明 S1 按钮正在被按下。while循环内的后续行对名为s2_push_button的PushButton实例执行相同的程序。
在我们检查了两个按钮的状态之后,调用time.sleep函数,并将0.5作为第二个参数的值,将执行延迟 500 毫秒,即 0.5 秒。
以下行将启动示例;不要忘记你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。
python iot_python_chapter_05_01.py
在运行示例之后,执行以下操作:
-
按下 S1 按钮 1 秒钟
-
按下 S2 按钮 1 秒钟
-
同时按下 S1 和 S2 按钮 1 秒钟
由于之前的操作,你将看到以下输出:
You are pressing S1.
You are pressing S2.
You are pressing S1.
You are pressing S2.
在这种情况下,我们正在使用轮询读取数字输入。mraa库还允许我们使用中断,并用 Python 声明中断处理程序。这样,每当用户按下按钮时,事件会生成中断,mraa库会调用指定的中断处理程序。如果你曾经从事基于事件的编程,你可以考虑事件和事件处理程序而不是中断和中断处理程序,这样你将很容易理解事情是如何工作的。
中断处理程序在不同的线程中运行,你可以为它们编写的代码有很多限制。例如,你无法在中断处理程序中使用基本类型。因此,在这种情况下,使用中断并不合适,而轮询由于我们必须在用户按下任意一个按钮时执行的任务,使得事情变得更容易。
与之前示例中的轮询读取数字输入相比,用于相同任务的轮询具有以下优点:
-
代码易于理解和阅读
-
流程易于理解,我们不必担心回调中运行的代码。
-
我们可以编写所有必要的代码来执行按钮按下时的动作,而不必担心与中断回调相关的特定限制。
-
我们不必担心多线程中运行的代码。
然而,与使用中断进行相同任务相比,使用轮询读取数字输入有以下缺点:
-
如果我们没有按住按钮特定的时间,代码可能无法检测到按钮被按下。
-
如果我们长时间按住按钮,代码将表现得好像按钮被多次按下。有时,我们不希望这种情况发生。
-
与中断触发的事件相比,循环消耗的资源更多,我们可能无法为其他任务提供这些资源。
在这种情况下,我们希望用户至少按住任意一个按钮半秒钟,因此我们不需要中断的优势。然而,我们将在本章后面使用中断。
读取按钮状态和运行 RESTful API。
现在,我们将集成检查两个按钮状态的代码到我们的 RESTful API 中。我们希望能够向 RESTful API 发出 HTTP 请求,并且我们也希望能够使用我们添加到面包板上的两个按钮。
我们必须让 Tornado 运行一个周期性回调,并在该回调中编写检查两个按钮状态的代码。我们将使用我们在上一章中创建最后一个版本的 RESTful API 时编写的代码,使用mraa库,并将此代码作为基准来添加新功能。示例代码文件为iot_python_chapter_04_03.py。
我们将向现有的BoardInteraction类添加两个类属性和三个类方法。示例代码文件为iot_python_chapter_05_02.py。
class BoardInteraction:
# The Red LED is connected to pin ~6
red_led = AnalogLed(6, 'Red')
# The Green LED is connected to Pin ~5
green_led = AnalogLed(5, 'Green')
# The Blue LED is connected to Pin ~3
blue_led = AnalogLed(3, 'Blue')
# The push button to reset colors
reset_push_button = PushButton(1)
# The push button to set colors to their maximum brightness
max_brightness_push_button = PushButton(0)
@classmethod
def set_min_brightness(cls):
cls.red_led.set_brightness(0)
cls.green_led.set_brightness(0)
cls.blue_led.set_brightness(0)
@classmethod
def set_max_brightness(cls):
cls.red_led.set_brightness(255)
cls.green_led.set_brightness(255)
cls.blue_led.set_brightness(255)
@classmethod
def check_push_buttons_callback(cls):
# Check whether the reset push button is pressed
if cls.reset_push_button.is_pressed:
print("You are pressing the reset pushbutton.")
cls.set_min_brightness()
# Check whether the maximum brightness push button is pressed
if cls.max_brightness_push_button.is_pressed:
print("You are pressing the maximum brightness pushbutton.")
cls.set_max_brightness()
之前的代码向BoardInteraction类添加了两个类属性:reset_push_button和max_brightness_push_button。reset_push_button类属性是一个PushButton实例,其pin属性设置为1。这样,该实例可以检查连接到 GPIO 引脚 1 的按钮的状态。max_brightness_push_button类属性是一个PushButton实例,其pin属性设置为0,因此,该实例可以检查连接到 GPIO 引脚 0 的按钮的状态。此外,之前的代码还向BoardInteraction类添加了以下类方法:
-
set_min_brightness: 使用0作为参数调用set_brightness方法,针对保存在red_led、green_led和blue_led类属性中的三个AnalogLed实例。这样,RGB LED 的三个组件将被关闭。 -
set_max_brightness:调用set_brightness方法,将255作为参数传递给保存在red_led、green_led和blue_led类属性中的三个AnalogLed实例。这样,RGB LED 的三个组件将以最大亮度级别打开。 -
check_push_buttons_callback:首先,通过评估代表复位按钮的PushButton实例的is_pressed属性值来检查复位按钮是否被按下。即,cls.reset_push_button。如果属性的值为True,代码将打印一条消息表明您正在按下复位按钮,并调用之前描述的cls.set_min_brightness类方法来关闭 RGB LED 的三个组件。然后,代码检查最大亮度按钮是否被按下,通过评估代表最大亮度按钮的PushButton实例的is_pressed属性值,即cls.max_brightness_push_button。如果属性的值为True,代码将打印一条消息表明您正在按下最大亮度按钮,并调用之前描述的cls.set_max_brightness类方法,以最大亮度级别打开 RGB LED 的三个组件。
小贴士
在 Python 中,在类方法标题之前添加@classmethod装饰器是必要的,以声明类方法。实例方法接收self作为第一个参数,但类方法接收当前类作为第一个参数,参数名称通常称为cls。在前面的代码中,我们使用cls来访问BoardInteraction类的类属性和类方法。
以下几行展示了我们必须添加到现有代码中的新类,以便通过 HTTP 请求设置最小和最大亮度。我们希望能够在我们的 RESTful API 中拥有与通过按钮可以控制的相同功能。代码添加了以下两个类:PutMinBrightnessHandler和PutMaxBrightnessHandler。示例代码文件为iot_python_chapter_05_02.py。
class PutMinBrightnessHandler(tornado.web.RequestHandler):
def put(self):
BoardInteraction.set_min_brightness()
response = dict(
red=BoardInteraction.red_led.brightness_value,
green=BoardInteraction.green_led.brightness_value,
blue=BoardInteraction.blue_led.brightness_value)
self.write(response)
class PutMaxBrightnessHandler(tornado.web.RequestHandler):
def put(self):
BoardInteraction.set_max_brightness()
response = dict(
red=BoardInteraction.red_led.brightness_value,
green=BoardInteraction.green_led.brightness_value,
blue=BoardInteraction.blue_led.brightness_value)
self.write(response)
代码声明了以下两个tornado.web.RequestHandler的子类:
-
PutMinBrightnessHandler:定义了调用BoardInteraction类的set_min_brightness类方法的put方法。然后,代码返回一个响应,其中包含已转换为连接到 RGB LED 红、绿、蓝阳极的 PWM 引脚输出占空比百分比的最低亮度级别。 -
PutMaxBrightnessHandler:定义了调用BoardInteraction类的set_max_brightness类方法的put方法。然后,代码返回一个响应,其中包含已转换为连接到 RGB LED 红、绿、蓝阳极的 PWM 引脚输出占空比百分比的最高亮度级别。
现在,有必要将高亮行添加到创建名为application的tornado.web.Application类实例的代码中,该实例包含构成 Web 应用程序的请求处理器列表,即正则表达式和tornado.web.RequestHandler子类的元组。示例代码文件为iot_python_chapter_05_02.py。
application = tornado.web.Application([
(r"/putredbrightness/([0-9]+)", PutRedBrightnessHandler),
(r"/putgreenbrightness/([0-9]+)", PutGreenBrightnessHandler),
(r"/putbluebrightness/([0-9]+)", PutBlueBrightnessHandler),
(r"/putrgbbrightness/r([0-9]+)g([0-9]+)b([0-9]+)",
PutRGBBrightnessHandler),
(r"/putminbrightness", PutMinBrightnessHandler),
(r"/putmaxbrightness", PutMaxBrightnessHandler),
(r"/getredbrightness", GetRedBrightnessHandler),
(r"/getgreenbrightness", GetGreenBrightnessHandler),
(r"/getbluebrightness", GetBlueBrightnessHandler),
(r"/version", VersionHandler)])
如我们之前的示例所示,代码创建了一个名为application的tornado.web.Application类实例,其中包含构成 Web 应用程序的请求处理器列表,即正则表达式和tornado.web.RequestHandler子类的元组。
最后,有必要将__main__方法替换为一个新的方法,因为我们想要运行一个周期性回调来检查两个按钮中的任何一个是否被按下。示例代码文件为iot_python_chapter_05_02.py。
if __name__ == "__main__":
print("Listening at port 8888")
application.listen(8888)
ioloop = tornado.ioloop.IOLoop.instance()
periodic_callback = tornado.ioloop.PeriodicCallback(BoardInteraction.check_push_buttons_callback, 500, ioloop)
periodic_callback.start()
ioloop.start()
如前例所示,__main__方法调用application.listen方法来为应用程序构建一个 HTTP 服务器,该服务器在端口号8888上定义了规则。然后,代码检索全局IOLoop实例并将其保存到ioloop局部变量中。我们必须使用该实例作为创建名为periodic_callback的tornado.ioloop.PeriodicCallback实例的一个参数。
PeriodicCallback实例允许我们安排一个指定的回调定期被调用。在这种情况下,我们指定BoardInteraction.check_push_buttons_callback类方法作为每 500 毫秒将被调用的回调。这样,我们指示 Tornado 每 500 毫秒运行一次BoardInteraction.check_push_buttons_callback类方法。如果该方法执行时间超过 500 毫秒,Tornado 将跳过后续调用以回到预定时间表。在代码创建PeriodicCallback实例后,下一行调用其start方法。
最后,调用ioloop.start()启动了使用application.listen创建的服务器。这样,Web 应用程序将处理接收到的请求,并且还会运行一个回调来检查按钮是否被按下。
以下行将启动 HTTP 服务器和我们的新版本 RESTful API。别忘了你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。
python iot_python_chapter_05_02.py
运行示例后,按下设置颜色为最大亮度的按钮一秒钟。RGB LED 将显示白光,你将看到以下输出:
You are pressing the maximum brightness pushbutton.
Red LED connected to PWM Pin #6 set to brightness 255.
Green LED connected to PWM Pin #5 set to brightness 255.
Blue LED connected to PWM Pin #3 set to brightness 255.
现在,按下设置颜色为最小亮度的按钮一秒钟。RGB LED 将关闭,你将看到以下输出:
You are pressing the reset pushbutton.
Red LED connected to PWM Pin #6 set to brightness 0.
Green LED connected to PWM Pin #5 set to brightness 0.
Blue LED connected to PWM Pin #3 set to brightness 0.
使用新的 RESTful API,我们可以组合以下 HTTP 动词和请求 URL:
PUT http://192.168.1.107:8888/putmaxbrightness
之前的请求路径将与之前添加的元组 (regexp, request_class) 匹配 (r"/putmaxbrightness", PutMaxBrightnessHandler),Tornado 将调用 PutMaxBrightnessHandler.put 方法。RGB LED 将显示白色光,就像你按下最大亮度按钮时发生的那样。以下行显示了 HTTP 服务器对三个 LED 设置的亮度级别的响应:
{
"blue": 255,
"green": 255,
"red": 255
}
以下 HTTP 动词和请求 URL 将关闭 RGB LED,就像我们按下设置颜色为最小亮度按钮时发生的那样:
PUT http://192.168.1.107:8888/putminbrightness
以下行显示了 HTTP 服务器对三个 LED 设置的亮度级别的响应:
{
"blue": 0,
"green": 0,
"red": 0
}
现在,按下设置颜色为最大亮度按钮一秒钟。RGB LED 将显示白色光。然后,以下三个 HTTP 动词和请求 URL 将检索每种颜色的亮度级别。所有请求都将返回 255 作为当前值。我们使用按钮设置亮度级别,但代码的效果与调用 API 来更改颜色相同。我们保持了应用程序的一致性。
GET http://192.168.1.107:8888/getredbrightness
GET http://192.168.1.107:8888/getgreenbrightness
GET http://192.168.1.107:8888/getbluebrightness
如果我们使用 HTTPie,以下命令将完成工作:
http –b GET http://192.168.1.107:8888/getredbrightness
http –b GET http://192.168.1.107:8888/getgreenbrightness
http –b GET http://192.168.1.107:8888/getbluebrightness
以下行显示了三个请求的响应:
{
"red": 255
}
{
"green": 255
}
{
"blue": 255
}
我们创建了可以在 API 调用和用户按下按钮时使用的方法。我们可以处理 HTTP 请求并在用户按下按钮时运行操作。当我们使用 Tornado 构建我们的 RESTful API 时,我们必须创建和配置一个 PeriodicCallback 实例,以便每 500 毫秒检查按钮是否被按下。
小贴士
当我们添加可以通过按钮或其他与板交互的电子组件控制的特性时,考虑一致性非常重要。在这种情况下,我们确保当用户按下按钮并更改三种颜色的亮度值时,通过 API 调用读取的亮度值与设置的值完全一致。我们使用面向对象的代码和相同的方法,因此保持一致性很容易。
使用 wiring-x86 库读取数字输入
到目前为止,我们一直在使用 mraa 库读取数字输入。然而,在第一章中,我们也安装了 wiring-x86 库。我们可以修改几行面向对象的代码,用 wiring-x86 库替换 mraa 库来检查按钮是否被按下。
当我们使用 wiring-x86 库创建我们 RESTful API 的最后一个版本时,我们将使用上一章编写的代码,并将此代码作为基准来添加新功能。示例代码文件为 iot_python_chapter_04_04.py。
首先,我们将创建一个PushButton类的新版本来表示连接到我们的板上的按钮,该按钮可以使用上拉或下拉电阻。以下行显示了与wiring-x86库一起工作的新PushButton类的代码。示例代码文件为iot_python_chapter_05_03.py。
from wiringx86 import GPIOGalileoGen2 as GPIO
class PushButton:
def __init__(self, pin, pull_up=True):
self.pin = pin
self.pull_up = pull_up
self.gpio = Board.gpio
pin_mode = self.gpio.INPUT_PULLUP if pull_up else self.gpio.INPUT_PULLDOWN
self.gpio.pinMode(pin, pin_mode)
@property
def is_pressed(self):
push_button_status = self.gpio.digitalRead(self.pin)
if self.pull_up:
# Pull-up resistor connected
return push_button_status == 0
else:
# Pull-down resistor connected
return push_button_status == 1
@property
def is_released(self):
return not self.is_pressed
我们只需要从PushButton类的先前代码中更改几行,即与mraa库一起工作的版本。与wiring-x86库交互的新行在之前的代码中突出显示。构造函数,即__init__方法接收与mraa库一起工作的PushButton类的相同参数。在这种情况下,此方法将Board.gpio类属性的一个引用保存到self.gpio中。然后,代码根据pull_up参数的值确定pin_mode局部变量的值。如果pull_up是true,则值将是self.gpio.INPUT_PULLUP,否则是self.gpio.INPUT_PULLDOWN。最后,构造函数使用接收到的pin作为其pin参数和pin_mode作为其模式参数调用self.gpio.pinMode方法。这样,我们配置引脚为具有适当的上拉或下拉电阻的数字输入引脚。所有的PushButton实例都将保存对创建GPIO类实例的同一Board.gpio类属性的引用,特别是wiringx86.GPIOGalileoGen2类,其debug参数设置为False以避免不必要的低级通信调试信息。
is_pressed属性调用 GPIO 实例(self.gpio)的digitalRead方法来检索配置为数字输入的引脚的数字值。self.pin属性指定analogRead方法调用的pin值。is_pressed属性和PushButton类的其余代码与使用mraa库的版本保持相同。
然后,我们需要对之前的例子中进行的相同编辑进行修改,以创建BoardInteraction类的新版本,添加PutMinBrightnessHandler和PutMaxBrightnessHandler类,创建tornado.web.Application实例以及创建和配置PeriodicCallback实例的新版本__main__方法。因此,我们 RESTful API 的其余代码与之前示例中使用的代码保持相同。没有必要对代码的其余部分进行修改,因为它将自动与新PushButton类一起工作,并且其构造函数或其属性的参数没有发生变化。
以下行将启动 HTTP 服务器和与wiring-x86库一起工作的我们新的 RESTful API 版本。不要忘记,你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux,正如前一章所述。
python iot_python_chapter_05_03.py
小贴士
我们可以按下按钮,然后发出我们在上一个示例中发出的相同 HTTP 请求,以检查我们是否可以使用wiring-x86库实现完全相同的结果。
使用中断检测按下按钮
之前,我们分析了与之前示例中轮询读取数字输入相比,使用中断执行相同任务的优缺点。如果我们长时间按下任何一个按钮,代码的行为就像按钮被多次按下一样。现在,我们不希望这种情况发生,因此我们将使用中断而不是轮询来检测按钮是否被按下。
在我们开始编辑代码之前,有必要修改我们现有的接线。问题是并非所有的 GPIO 引脚都支持中断。实际上,编号为 0 和 1 的引脚不支持中断,而我们的按钮连接到了这些引脚上。在第一章中,理解和设置基础物联网硬件,当我们学习到英特尔 Galileo Gen 2 板上的 I/O 引脚时,我们了解到带有波浪线符号(~)作为编号前缀的引脚可以用作 PWM 输出引脚。事实上,带有波浪线符号(~)作为编号前缀的引脚也支持中断。
因此,我们可以将连接到关闭三个颜色的复位按钮的线从引脚1移到引脚11**,并将连接到将三个颜色设置为最大亮度的按钮的线从引脚**0**移到引脚**10。
以下图显示了连接到面包板的组件、必要的接线以及从英特尔 Galileo Gen 2 板到面包板的接线。该示例的 Fritzing 文件为iot_fritzing_chapter_05_04.fzz,以下图片是面包板视图:

以下图片显示了用符号表示的电子元件的电路图:

板上符号中标记为D10 PWM/SS的 GPIO 引脚连接到S2按钮,R4电阻是其上拉电阻。板上符号中标记为D11 PWM/MOSI的 GPIO 引脚连接到S1按钮,R5电阻是其上拉电阻。这样,当S2按钮被按下时,GPIO 引脚编号 10 将是低电平,当S1按钮被按下时,GPIO 引脚编号 11 将是低电平。
提示
当按钮被按下时,信号将从高电平降至低电平,因此,我们对当信号降至低电平时产生的中断感兴趣,因为这表明按钮已被按下。如果用户持续按下按钮,信号不会多次下降,GPIO 引脚将保持在低电平。因此,当我们观察从高电平降至低电平的过程中,即使用户长时间按下按钮,也只会触发一次中断,我们不会对中断处理代码进行多次调用。
请记住,S1按钮位于面包板的左侧,而S2按钮位于右侧。现在,是时候对连线进行更改了。在从板上的引脚上拔掉任何电线之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 灯熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。完成连线更改后,我们将编写 Python 代码来检测用户按下按钮时使用中断而不是轮询。
当我们使用mraa库创建我们 RESTful API 的最后一个版本时,我们将使用之前示例中编写的代码,并将此代码作为基准来添加新功能。示例代码文件为iot_python_chapter_05_02.py。
我们将创建一个新的PushButtonWithInterrupt类来表示连接到我们板上的按钮,该按钮可以使用上拉或下拉电阻,并将指定当按钮被按下时需要调用的回调,即中断处理程序。当按钮被按下时,将发生中断,指定的回调将作为中断处理程序执行。以下行显示了与mraa库一起工作的新PushButtonWithInterrupt类的代码。示例代码文件为iot_python_chapter_05_04.py。
import mraa
import time
from datetime import date
class PushButtonWithInterrupt:
def __init__(self, pin, pyfunc, args, pull_up=True):
self.pin = pin
self.pull_up = pull_up
self.gpio = mraa.Gpio(pin)
self.gpio.dir(mraa.DIR_IN)
mode = mraa.EDGE_FALLING if pull_up else mraa.EDGE_RISING
result = self.gpio.isr(mode, pyfunc, args)
if result != mraa.SUCCESS:
raise Exception("I could not configure ISR on pin {0}".format(pin))
def __del__(self):
self.gpio.isrExit()
在创建PushButtonWithInterrupt类的实例时,我们必须指定以下参数:
-
在
pin参数中,按钮连接到的引脚号 -
当中断被触发时将被调用的函数,即中断处理函数,在
pyfunc参数中 -
将传递给中断处理函数的参数,在
args参数中
如果我们没有指定额外的值,可选的pull_up参数将为True,实例将像按钮连接上拉电阻一样工作。如果我们使用下拉电阻,我们必须在pull_up参数中传递False。
构造函数,即 __init__ 方法,创建一个新的 mraa.Gpio 实例,将接收到的 pin 作为其 pin 参数,将其引用保存到 gpio 属性中,并调用其 dir 方法来配置引脚为输入引脚(mraa.DIR_IN)。然后,代码根据 pull_up 参数的值确定 mode 局部变量的值。如果 pull_up 为 true,则值将为 mraa.EDGE_FALLING 和 mraa.EDGE_RISING;否则。mode 局部变量持有将触发中断的边缘模式。当我们使用上拉电阻并且用户按下按钮时,信号将从高电平下降到低电平,因此我们希望边缘下降场景触发中断,以指示按钮已被按下。
然后,代码使用接收到的 pin 作为其 pin 参数,局部变量 mode 作为其 mode 参数,以及接收到的 pyfunc 和 args 作为其 pyfunc 和 args 参数来调用 self.gpio.isr 方法。这样,我们设置了一个回调,当引脚值改变(即按钮被按下)时将被调用。因为我们之前已经确定了 mode 局部变量的适当值,所以我们将根据上拉或下拉电阻的使用配置适当的边缘模式,以便在按钮被按下时触发中断。如前所述,并非所有 GPIO 引脚都支持中断,因此有必要检查调用 self.gpio.isr 方法的返回结果。如果之前通过调用 self.gpio.isr 方法已经将中断处理程序设置到引脚上,则不会返回 mraa.SUCCESS 值。
PushButtonWithInterrupt 类还声明了一个 __del__ 方法,该方法将在 Python 从内存中删除此类的实例之前被调用,即当对象变得不可访问并被垃圾回收机制删除时。该方法只是调用 self.gpio.isrExit 方法来移除与引脚关联的中断处理程序。
我们将替换现有 BoardInteraction 类中的两个类属性。我们将不再使用 PushButton 实例,而是使用 PushButtonWithInterrupt 实例。类中声明的类方法与作为基准使用的代码中保持相同,但它们不包括在下述行中。示例代码的文件名为 iot_python_chapter_05_04.py。
class BoardInteraction:
# The Red LED is connected to pin ~6
red_led = AnalogLed(6, 'Red')
# The Green LED is connected to Pin ~5
green_led = AnalogLed(5, 'Green')
# The Blue LED is connected to Pin ~3
blue_led = AnalogLed(3, 'Blue')
# The push button to reset colors
reset_push_button = PushButtonWithInterrupt(11, set_min_brightness_callback, set_min_brightness_callback)
# The push button to set colors to their maximum brightness
max_brightness_push_button = PushButtonWithInterrupt(10, set_max_brightness_callback, set_max_brightness_callback)
突出的代码行声明了BoardInteraction类的两个类属性:reset_push_button和max_brightness_push_button。reset_push_button类属性是一个PushButtonWithInterrupt的实例,其pin属性设置为11,中断处理程序设置为稍后我们将声明的set_min_brightness_callback函数。这样,该实例将在用户按下连接到 GPIO 引脚编号 11 的按钮时调用set_min_brightness_callback函数进行所有必要的配置。max_brightness_push_button类属性是一个PushButtonWithInterrupt的实例,其pin属性设置为10,因此,将在用户按下连接到 GPIO 引脚编号 10 的按钮时调用set_max_brightness_callback函数进行所有必要的配置。
现在,有必要声明当中断被触发时将被调用的函数:set_min_brightness_callback和set_max_brightness_callback。请注意,这些函数被声明为函数,而不是任何类的成员方法。
def set_max_brightness_callback(args):
print("You have pressed the maximum brightness pushbutton.")
BoardInteraction.set_max_brightness()
def set_min_brightness_callback(args):
print("You have pressed the reset pushbutton.")
BoardInteraction.set_min_brightness()
在前面的代码中声明的两个函数会打印一条消息,指示已按下特定按钮,并调用BoardInteraction.set_max_brightness或BoardInteraction.set_min_brightness类方法。我们已经从之前的示例中知道了这些类方法,并且我们没有对它们进行任何更改。
最后,有必要用一个新的方法替换__main__方法,因为我们不再需要运行周期性回调了。现在,我们的PushButtonWithInterrupt实例配置了当按下按钮时将被调用的中断处理程序。示例的代码文件是iot_python_chapter_05_04.py。
if __name__ == "__main__":
print("Listening at port 8888")
application.listen(8888)
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.start()
当__main__方法开始运行时,BoardInteraction类已经执行了创建两个PushButtonWithInterrupt实例的代码,因此,每当按下按钮时,中断处理程序都会运行。__main__方法只是构建并启动 HTTP 服务器。
以下行将启动 HTTP 服务器和我们的新版本 RESTful API。别忘了你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。
python iot_python_chapter_05_04.py
运行示例后,按下设置颜色为最大亮度的按钮 5 秒钟。RGB LED 将显示白色光,你将看到以下输出:
You are pressing the maximum brightness pushbutton.
Red LED connected to PWM Pin #6 set to brightness 255.
Green LED connected to PWM Pin #5 set to brightness 255.
Blue LED connected to PWM Pin #3 set to brightness 255.
你按下了按钮 5 秒钟,但输出显示的消息表明你只按了一次按钮。当你按下按钮时,GPIO 引脚编号 10 的信号从高变低一次,因此触发了mraa.EDGE_FALLING中断,并执行了配置的中断处理程序(set_max_brightness_callback)。你继续按住按钮,但信号保持在低值,因此没有再次触发中断。
小贴士
显然,当你只想在按下按钮一次(即使按得很久)时运行代码,中断处理程序的用法提供了轮询所难以实现的必要精度。
现在,按下设置颜色为最低亮度的按钮 10 秒钟。RGB LED 将关闭,你将看到以下输出:
You are pressing the reset pushbutton.
Red LED connected to PWM Pin #6 set to brightness 0.
Green LED connected to PWM Pin #5 set to brightness 0.
Blue LED connected to PWM Pin #3 set to brightness 0.
就像其他按钮一样,你按下了按钮很多秒,但显示的消息表明你只按了一次按钮。当你按下按钮时,GPIO 引脚 11 的信号从高电平变为低电平一次,因此,mraa.EDGE_FALLING中断被触发,配置的中断处理程序(set_min_brightness_callback)被执行。
小贴士
我们可以发出与之前示例中相同的 HTTP 请求,以检查我们是否可以使用与使用中断处理程序运行 HTTP 服务器的新代码实现完全相同的结果。
我们可以处理 HTTP 请求,并在用户按下按钮时运行中断处理程序。与之前版本相比,我们提高了准确性,因为代码表现得好像用户长时间按住按钮时按钮被按了很多次。此外,我们还移除了周期性回调。
小贴士
每当我们需要读取数字输入时,我们可以根据我们项目中的具体需求在轮询或中断处理程序之间进行选择。有时,中断处理程序是最佳解决方案,但在其他情况下轮询更为合适。非常重要的一点是,wiring-x86库不允许我们使用中断处理程序来处理数字输入,因此,如果我们决定使用它们,我们必须使用mraa库。
测试你的知识
-
由于在按钮上使用了上拉电阻,当按钮按下连接到 GPIO 引脚时,我们将读取以下值:
-
低值(0V)。
-
高值,即 IOREF 电压。
-
电压值在 1V 到 3.3V 之间。
-
-
由于在按钮上使用了上拉电阻,当按钮释放连接到 GPIO 引脚时,我们将读取以下值:
-
低值(0V)。
-
高值,即 IOREF 电压。
-
电压值在 1V 到 3.3V 之间。
-
-
如果我们通过轮询读取连接到 GPIO 引脚的按钮状态,循环每 0.5 秒运行一次,并且用户持续按下按钮 3 秒钟:
-
代码将表现得好像按钮被按了多次。
-
代码将表现得好像按钮被按了一次。
-
代码将表现得好像按钮从未被按下。
-
-
我们有一个中断处理程序,用于按钮,中断边缘模式设置为
mraa.EDGE_FALLING,按钮通过上拉电阻连接。如果用户持续按下按钮 3 秒钟:-
代码将表现得好像按钮被按了多次。
-
代码将表现得好像按钮只被按下了一次。
-
代码将表现得好像按钮从未被按下。
-
-
在英特尔 Galileo Gen 2 板上,带有以下符号作为前缀的引脚可以在
mraa库中配置为数字输入的中断处理程序:-
哈希符号(#)。
-
美元符号($)。
-
波浪符号(~)。
-
摘要
在本章中,我们了解了上拉和下拉电阻的区别,以及如何使用mraa和wiring-x86库读取按钮的状态。我们了解了使用轮询读取按钮状态与使用中断和中断处理程序工作的区别。
我们创建了统一的代码,允许用户使用面包板上的按钮或 HTTP 请求执行相同的操作。我们将响应按钮状态变化的代码与使用 Tornado Web 服务器构建的 RESTful API 相结合。与前面的章节一样,我们利用了 Python 的面向对象特性,并创建了类来封装按钮和必要的配置,使用mraa和wiring-x86库。我们的代码易于阅读和理解,并且我们可以轻松切换底层低级库。
现在我们能够以不同的方式和配置读取数字输入,这使得用户在设备处理 HTTP 请求的同时能够与之交互,我们可以利用板上的更复杂的通信功能,并利用其存储功能,这些是下一章的主题。
第六章. 使用模拟输入和本地存储
在本章中,我们将使用模拟输入将来自真实环境的定量值转换为定性值,我们将使用这些值来触发动作。我们将:
-
理解模拟输入的工作原理
-
了解模拟数字转换器分辨率的影响
-
使用模拟引脚和
mraa库测量电压 -
在分压器中包含光敏电阻,并将模拟输入引脚与电压源连接
-
将可变电阻转换为电压源
-
使用模拟输入和
mraa库确定黑暗程度 -
当环境光线变化时触发动作
-
使用 wiring-x86 库控制模拟输入
-
使用不同的本地存储选项来记录事件
理解模拟输入
在第一章 理解和设置基础物联网硬件 中,我们了解到英特尔 Galileo Gen 2 板提供了从A0到A5编号的六个模拟输入引脚,位于板的前面板的右下角。可以测量从 0V(地)到配置的IOREF跳线位置(默认为 5V)的值,该板为模拟数字转换器提供 12 位的分辨率。因此,我们可以检测到 4096 个不同的值(2¹² = 4096),或 4096 个单位,其值从 0 到 4095(包含),其中 0 代表 0V,4095 表示 5V。
小贴士
如果你有其他 Arduino 板的经验,你必须考虑到英特尔 Galileo Gen 2 板不使用标记为AREF的引脚。在其他 Arduino 板上,你可以使用此引脚来设置模拟数字转换过程的模拟参考电压。当我们使用英特尔 Galileo Gen 2 板时,模拟引脚的最大值始终将由IOREF跳线位置(5V 或 3.3V)控制,并且无法为模拟输入使用任何外部参考。在我们的所有示例中,我们将使用IOREF跳线的默认位置,因此最大值始终为 5V。
我们只需要应用一个线性函数将模拟引脚读取的原始值转换为输入电压值。如果我们使用 12 位的分辨率,检测到的值将具有最小差异或步长为 5V / 4095 = 0.001220012 V,大约为 1.22 mV(毫伏)或 1.22E-03 V。我们只需要将模拟引脚读取的原始值乘以 5,然后除以 4095。
下面的图表显示了从模拟引脚读取的值在横轴(x-轴)上,以及它在纵轴(y-轴)上表示的相应浮点电压值。

之前图表的方程式是 y = x / 4095 * 5,具体来说 voltage_value = analog_pin_read_value / 4095 * 5。我们可以在我们的 Python 解释器中运行以下代码来查看输出,其中包括使用公式从模拟引脚读取的每个原始值(从0到4095,包括)可以生成的所有电压值。
for analog_pin_read_value in range(0, 4096):
print(analog_pin_read_value / 4095.0 * 5.0)
提示
我们也可以使用较低的分辨率,例如 10 位分辨率,我们就能检测到更少的不同的值,具体是 1024 个不同的值(2¹⁰ = 1024),或 1024 个单位,从0到1023(包括)。在这种情况下,值的最小差异或步长将是 5V / 1023 = 0.004887585V,大约是 4.89mV(毫伏)或 4.89E-03 V。如果我们决定使用这个较低的分辨率,我们只需将模拟引脚读取的原始值乘以五,然后除以 1023。
使用电压源连接模拟输入引脚
理解如何从模拟引脚读取值并将这些值映射回电压值的最简单方法是通过一个非常简单的例子来操作。我们将连接一个电源到模拟输入引脚之一,具体来说是一个串联两个 AA 或 AAA 1.25 V 可充电电池的电池组。也可以使用串联的两个 AA 或 AAA 1.5 V 标准电池。请注意,两个可充电电池串联时的最大电压将是 2.5 V(1.25 V * 2),而两个标准电池串联时的最大电压将是 3 V(1.5 V * 2)。
我们将使用标记为A0的模拟引脚连接到电池组的正极(+)。别忘了电池组的正极(+)连接到电池的乳头。完成必要的接线后,我们将编写 Python 代码来测量电池组的电压。这样,我们将读取将模拟值转换为数字表示的结果,并将其映射到电压值。为了使用这个例子,我们需要以下部件:
-
两个 AA 或 AAA 1.25 V 可充电电池或两个 AA 或 AAA 1.5 V 标准电池。
-
一个合适的电池夹,用于将两个选定的电池串联并简化接线。例如,如果你使用两个 AA 1.25 可充电电池,你需要一个 2 x AA 电池夹。
-
一个 2200Ω(2k2Ω)的 5%容差(红红红金)电阻。
以下图像显示了电池夹、连接到面包板的电阻、必要的接线以及从英特尔 Galileo Gen 2 板到面包板的接线。该示例的 Fritzing 文件为iot_fritzing_chapter_06_01.fzz,以下图像是面包板视图:

以下图示显示了用符号表示电子组件的电路图:

如前图所示,板符号上标记为 A0 的模拟输入引脚通过电阻连接到电源的正极。电源的负极连接到地。
现在,是时候进行所有必要的接线了。在添加或移除任何线从板上的引脚之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
使用模拟输入和 mraa 库测量电压
我们将创建一个新的 VoltageInput 类来表示连接到我们的板上的电压源,具体来说,是连接到模拟输入引脚。以下行显示了与 mraa 库一起工作的新 VoltageInput 类的代码。示例代码文件为 iot_python_chapter_06_01.py。
import mraa
import time
class VoltageInput:
def __init__(self, analog_pin):
self.analog_pin = analog_pin
self.aio = mraa.Aio(analog_pin)
# Configure ADC resolution to 12 bits (0 to 4095)
self.aio.setBit(12)
@property
def voltage(self):
raw_value = self.aio.read()
return raw_value / 4095.0 * 5.0
在创建 VoltageInput 类的实例时,我们必须指定电压源连接到的模拟引脚编号,analog_pin 是必需的参数。构造函数,即 __init__ 方法,使用接收到的 analog_pin 作为其 pin 参数创建一个新的 mraa.Aio 实例,将其引用保存到 aio 属性中,并调用其 setBit 方法来配置模拟数字转换器的分辨率为 12 位,即提供 4096 个可能的值来表示从 0 到 5V。
该类定义了一个 voltage 属性,它调用相关 mraa.Aio 实例(self.aio)的 read 方法来从模拟引脚检索原始值,并将其保存到 raw_value 变量中。然后,代码返回将 raw_value 除以 4095 并乘以 5 的结果。这样,该属性返回从读取函数返回的原始值转换的电压值。
现在,我们可以编写使用新的 VoltageInput 类来创建电池包实例并轻松检索电压值的代码。新类执行必要的计算,将读取的值映射到电压值,因此我们只需检查 voltage 属性的值,无需担心关于模拟数字转换器和其分辨率的详细信息。
现在,我们将编写一个循环,每秒检索一次电压值。示例代码文件为 iot_python_chapter_06_01.py。
if __name__ == "__main__":
v0 = VoltageInput(0)
while True:
print("Voltage at pin A0: {0}".format(v0.voltage))
# Sleep 1 second
time.sleep(2)
第一行创建了一个之前编码的 VoltageInput 类的实例,其中 analog_pin 参数的值为 0。这样,该实例将读取标记为 A0 的引脚上的模拟值,该引脚通过电阻连接到电源的正极。
然后,代码将无限循环运行,也就是说,直到你通过按Ctrl + C或按下停止过程的按钮来中断执行。该循环每两秒打印一次A0引脚的电压值。以下是在使用两个电量略有下降的可充电电池执行代码时生成的示例输出行:
Voltage at pin A0: 2.47130647131
将光敏电阻连接到模拟输入引脚
现在,我们将使用光敏电阻,也就是光传感器,具体来说,这是一种电子元件,它提供了一个可变电阻,该电阻的阻值会根据入射光的强度而变化。随着入射光强度的增加,光敏电阻的阻值减小,反之亦然。
小贴士
光敏电阻也被称为LDR(即光敏电阻)或光电管。请注意,光敏电阻并不是最佳的光感测元件,但当我们没有达到一秒的延迟问题时,它们在轻松确定我们是否处于黑暗环境中时非常有用。
我们无法使用我们的板子测量电阻值。然而,我们可以读取电压值,因此,我们将使用一个电压分压器配置,其中包括光敏电阻作为其两个电阻之一。当光敏电阻接收到大量光线时,电压分压器将输出高电压值;当光敏电阻处于暗区,即接收到少量或没有光线时,它将输出低电压值。
在之前的例子中,我们学习了如何从模拟引脚读取值并将这些值映射回电压值。我们将使用这些知识来确定何时变暗。一旦我们理解了传感器的工作原理,我们将对光条件的变化做出反应,并记录特定场景的数据。
我们将使用标有A0的模拟引脚来连接包含光敏电阻的电压分压器的正极(+)。完成必要的接线后,我们将编写 Python 代码来确定我们是否处于黑暗环境中。这样,我们将读取将电阻值转换为电压的结果,然后将这个模拟值转换为它的数字表示。正如我们在之前的例子中学到的,我们将读取的数字值映射到电压值,然后我们将这个电压值映射到黑暗测量值。听起来很复杂,但实际上比听起来容易得多。我们需要以下部分来处理这个例子:
-
光敏电阻
-
一个 10,000Ω(10kΩ)的电阻,公差为 5%(棕色 黑色 橙色 金色)
以下图表显示了连接到面包板的光敏电阻和电阻,必要的布线和从英特尔 Galileo Gen 2 板到面包板的布线。该示例的 Fritzing 文件为iot_fritzing_chapter_06_02.fzz,以下图片是面包板视图:

以下图片显示了用符号表示的电子组件的电路图:

如前图所示,板符号上标记为A0的 GPIO 引脚连接到由名为LDR1的光敏电阻和 5%公差为 10kΩ的电阻R1构建的分压器。LDR1光敏电阻连接到IOREF引脚。我们已经知道标记为IOREF的引脚为我们提供 IOREF 电压,即在我们的实际配置中为 5V。R1电阻连接到GND(地)。
现在,是时候进行所有必要的布线了。在添加或从板上的引脚移除任何电线之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从英特尔 Galileo Gen 2 板上拔掉电源。
使用模拟输入和 mraa 库确定黑暗程度
我们将创建一个新的DarknessSensor类来表示包含在分压器中并连接到我们的板上的光敏电阻,具体来说,是连接到模拟输入引脚。因为我们已经编写了读取和转换模拟输入的代码,所以我们将使用之前创建的VoltageInput类。以下行显示了与mraa库一起工作的新DarknessSensor类的代码。该示例的代码文件为iot_python_chapter_06_02.py。
import mraa
import time
class DarknessSensor:
# Light level descriptions
light_extremely_dark = "extremely dark"
light_very_dark = "very dark"
light_dark = "just dark"
light_no_need_for_a_flashlight = \
"there is no need for a flashlight"
# Maximum voltages that determine the light level
extremely_dark_max_voltage = 2.0
very_dark_max_voltage = 3.0
dark_max_voltage = 4.0
def __init__(self, analog_pin):
self.voltage_input = VoltageInput(analog_pin)
self.voltage = 0.0
self.ambient_light = self.__class__.light_extremely_dark
self.measure_light()
def measure_light(self):
self.voltage = self.voltage_input.voltage
if self.voltage < self.__class__.extremely_dark_max_voltage:
self.ambient_light = self.__class__.light_extremely_dark
elif self.voltage < self.__class__.very_dark_max_voltage:
self.ambient_light = self.__class__.light_very_dark
elif self.voltage < self.__class__.dark_max_voltage:
self.ambient_light = self.__class__.light_dark
else:
self.ambient_light = self.__class__.light_no_need_for_a_flashlight
当我们在analog_pin必需参数中创建DarknessSensor类的实例时,我们必须指定连接到包含光敏电阻的分压器的模拟引脚编号。构造函数,即__init__方法,使用接收到的analog_pin作为其analog_pin参数创建一个新的VoltageInput实例,并将其引用保存在voltage_input属性中。然后,构造函数创建并初始化两个属性:voltage和ambient_light。最后,构造函数调用measure_light方法。
该类定义了一个measure_light方法,该方法通过检查self.voltage_input.voltage属性在voltage属性(self.voltage)中检索到的电压值来保存电压值。这样,代码可以检查存储在电压属性中的值是否低于确定光级的三个最大电压值,并为ambient_light属性(self.ambient_light)设置适当的值。
该类定义了以下三个类属性,这些属性确定了确定每个光级的最大电压值:
-
extremely_dark_max_voltage:如果检索到的电压低于 2V,这意味着环境非常暗 -
very_dark_max_voltage: 如果检索到的电压低于 3V,这意味着环境非常暗 -
dark_max_voltage。如果检索到的电压低于 4V,这意味着环境只是暗
小贴士
这些值是为特定光敏电阻和环境条件配置的。你可能需要根据包含在分压器中的光敏电阻检索到的电压值设置不同的值。一旦运行示例,你可以检查电压值并对之前解释过的类属性中存储的电压值进行必要的调整。记住,当入射光增加时,电压值会更高,即更接近 5V。因此,最暗的环境,测量的电压越低。
我们的目标是将一个定量值,特别是电压值,转换为定性值,即一个能够解释真实环境中真实情况的值。该类定义了以下四个类属性,指定光级描述并确定在调用measure_light方法后电压值将被转换为四个光级中的哪一个:
-
light_extremely_dark -
light_very_dark -
light_dark -
light_no_need_for_a_flashlight
现在,我们可以编写使用新的DarkSensor类创建分压器中包含的光敏电阻实例的代码,并轻松打印光条件描述。新类使用之前创建的VoltageInput类进行必要的计算,将读取的值映射到电压值,然后将其转换为定性值,为我们提供光条件描述。现在,我们将编写一个循环,每两秒检查一次光条件是否改变。示例的代码文件是iot_python_chapter_06_02.py。
if __name__ == "__main__":
darkness_sensor = DarknessSensor(0)
last_ambient_light = ""
while True:
darkness_sensor.measure_light()
new_ambient_light = darkness_sensor.ambient_light
if new_ambient_light != last_ambient_light:
# The ambient light value changed
last_ambient_light = new_ambient_light
print("Darkness level: {0}".format(new_ambient_light))
# Sleep 2 seconds
time.sleep(2)
第一行创建了一个之前编写的DarknessSensor类的实例,将0作为analog_pin参数的值,并将实例保存在darkness_sensor局部变量中。这样,该实例将使用VoltageInput类的实例从标记为A0的引脚读取模拟值。然后,代码将last_ambient_light局部变量初始化为空字符串。
然后,代码将无限循环运行,即直到你通过按Ctrl + C或按下停止过程的按钮来中断执行。在这种情况下,如果你使用具有远程开发功能的 Python IDE 运行代码在你的板上,循环将调用darkness_sensor.measure_light方法来检索当前的光线条件,并将更新的darkness_sensor.ambient_light值保存在new_ambient_light局部变量中。然后,代码检查new_ambient_light值是否与last_ambient_light不同。如果它们不同,这意味着环境光线已经改变,因此,它将last_ambient_light的值设置为new_ambient_light,并打印存储在new_ambient_light中的环境光线描述。
当环境光线从最后打印的值变化时,循环打印环境光线描述,并且每两秒检查一次环境光线。以下行将启动示例。别忘了你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。
python iot_python_chapter_06_02.py
在运行示例之后,执行以下操作:
-
使用智能手机或手电筒在光敏电阻上诱导光线
-
用你的手在光敏电阻上产生阴影
-
减少环境中的光线,但不是最小值,只是让它稍微暗一些
-
将环境中的光线减少到最小,一个完全没有光线的完全黑暗环境
前述动作的结果,你应该看到以下输出:
Darkness level: there is no need for a flashlight
Darkness level: just dark
Darkness level: very dark
Darkness level: extremely dark
环境光线变化时的触发动作
在之前的示例中,我们使用 PWM 来设置 RGB LED 的红色、绿色和蓝色组件的亮度级别。现在,我们将添加一个 RGB LED,并将基于光敏电阻检测到的环境光线为其三个组件设置亮度级别。我们将像在第四章中处理此组件的示例那样布线 RGB LED,使用 RESTful API 和脉宽调制。我们将使用以下 PWM 输出引脚:
-
将~6引脚连接到红色 LED 的正极引脚
-
将~5引脚连接到绿色 LED 的正极引脚
-
将~3引脚连接到蓝色 LED 的正极引脚。
我们需要以下额外的部件来使用此示例:
-
一个常见的共阴极 5mm RGB LED
-
三个 270Ω、5%容差的电阻(红紫棕金)
以下图显示了连接到面包板的组件,必要的布线和从 Intel Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件为iot_fritzing_chapter_06_03.fzz,以下图片是面包板视图:

以下图片显示了用符号表示的电子组件的原理图:

如前图所示,板上的符号中标记为D3 PWM、D5 PWM和D6 PWM的三个具有 PWM 功能的 GPIO 引脚连接到一个 270Ω电阻,该电阻连接到每个 LED 颜色的阳极引脚,而公共阴极连接到地。
现在,是时候将组件插入面包板并完成所有必要的布线了。在添加或移除任何线之前,别忘了关闭 Yocto Linux,等待所有板载 LED 熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
我们将添加代表连接到我们的板上的 LED 的AnalogLed类的代码,该 LED 的亮度级别可以从 0 到 255。我们在第四章中创建了此类,使用 RESTful API 和脉宽调制,示例代码文件为iot_python_chapter_04_02.py。
我们将创建一个新的BoardInteraction类来创建我们的DarknessSensor类的一个实例以及 RGB LED 每个组件的一个实例,以便轻松控制它们的亮度级别。以下行显示了BoardInteraction类的代码。示例代码文件为iot_python_chapter_06_03.py:
class BoardInteraction:
# The photoresistor included in the voltage divider
# is connected to analog PIN A0
darkness_sensor = DarknessSensor(0)
# The Red LED is connected to GPIO pin ~6
red_led = AnalogLed(6, 'Red')
# The Green LED is connected to GPIO Pin ~5
green_led = AnalogLed(5, 'Green')
# The Blue LED is connected to GPIO Pin ~3
blue_led = AnalogLed(3, 'Blue')
@classmethod
def set_rgb_led_brightness(cls, brightness_level):
cls.red_led.set_brightness(brightness_level)
cls.green_led.set_brightness(brightness_level)
cls.blue_led.set_brightness(brightness_level)
@classmethod
def update_leds_brightness(cls):
if cls.darkness_sensor.ambient_light == DarknessSensor.light_extremely_dark:
cls.set_rgb_led_brightness(255)
elif cls.darkness_sensor.ambient_light == DarknessSensor.light_very_dark:
cls.set_rgb_led_brightness(128)
elif cls.darkness_sensor.ambient_light == DarknessSensor.light_dark:
cls.set_rgb_led_brightness(64)
else:
cls.set_rgb_led_brightness(0)
BoardInteraction类声明了四个类属性:darkness_sensor、red_led、green_led和blue_led。第一个类属性保存了DarknessSensor类的新实例,最后三个类属性保存了之前导入的AnalogLed类的新实例,分别代表连接到引脚6**、**5和~3的红、绿、蓝 LED。然后,BoardInteraction类声明了以下两个类方法:
-
set_rgb_led_brightness:将brightness_level参数接收到的相同亮度级别设置为 RGB LED 的三个组件。 -
update_leds_brightness:根据 DarknessSensor 实例(cls.darkness_sensor)的ambient_light值设置 RGB LED 的三个组件的亮度级别。如果非常暗,亮度级别将为 255。如果很暗,亮度级别将为 128。如果暗,亮度级别将为 64。否则,RGB LED 将完全关闭。
现在,我们可以编写一个代码,使用新的BoardInteraction类来测量环境光线并根据获取的值设置 RGB LED 的亮度。正如我们之前的示例一样,我们只有在环境光线值从当前值变化时才会进行更改。我们将编写一个循环,每两秒检查一次光线条件是否发生变化。示例代码文件为iot_python_chapter_06_03.py。
last_ambient_light = ""
while True:
BoardInteraction.darkness_sensor.measure_light()
new_ambient_light = BoardInteraction.darkness_sensor.ambient_light
if new_ambient_light != last_ambient_light:
# The ambient light value changed
last_ambient_light = new_ambient_light
print("Darkness level: {0}".format(new_ambient_light))
BoardInteraction.update_leds_brightness()
# Sleep 2 seconds
time.sleep(2)
第一行初始化last_ambient_light局部变量为空字符串。然后,代码无限循环运行,即直到你中断执行。循环调用BoardInteraction.darkness_sensor.measure_light方法来检索当前光线条件,并将更新的BoardInteraction.darkness_sensor.ambient_light值保存到new_ambient_light局部变量中。然后,代码检查new_ambient_light值是否与last_ambient_light不同。如果它们不同,这意味着环境光线已经改变,因此,它将last_ambient_light的值设置为new_ambient_light,打印存储在new_ambient_light中的环境光线描述,并调用BoardInteraction.update_leds_brightness方法根据环境光线设置 RGB LED 的亮度。
以下行将开始示例。别忘了你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。
python iot_python_chapter_06_03.py
在运行示例之后,执行以下操作,你将看到 RGB LED 的亮度级别按以下方式变化:
-
使用智能手机或手电筒在光敏电阻上产生光线。RGB LED 将保持关闭。
-
用你的手在光敏电阻上产生阴影。RGB LED 将以昏暗的灯光打开。
-
减少环境中的光线,但不是最小,只是让它变得有点暗。RGB LED 将增加其亮度。
-
将环境中的光线减少到最小,一个完全没有光线的完全黑暗环境。RGB LED 将增加其亮度到最大级别。
-
使用智能手机或手电筒再次在光敏电阻上产生光线。RGB LED 将关闭。
由于之前的操作,你应该看到以下输出:
Darkness level: there is no need for a flashlight
Red LED connected to PWM Pin #6 set to brightness 0.
Green LED connected to PWM Pin #5 set to brightness 0.
Blue LED connected to PWM Pin #3 set to brightness 0.
Darkness level: just dark
Red LED connected to PWM Pin #6 set to brightness 64.
Green LED connected to PWM Pin #5 set to brightness 64.
Blue LED connected to PWM Pin #3 set to brightness 64.
Darkness level: very dark
Red LED connected to PWM Pin #6 set to brightness 128.
Green LED connected to PWM Pin #5 set to brightness 128.
Blue LED connected to PWM Pin #3 set to brightness 128.
Darkness level: extremely dark
Red LED connected to PWM Pin #6 set to brightness 255.
Green LED connected to PWM Pin #5 set to brightness 255.
Blue LED connected to PWM Pin #3 set to brightness 255.
Darkness level: there is no need for a flashlight
Red LED connected to PWM Pin #6 set to brightness 0.
Green LED connected to PWM Pin #5 set to brightness 0.
Blue LED connected to PWM Pin #3 set to brightness 0.
我们编写了易于阅读和理解的面向对象 Python 代码。借助mraa库,我们能够在环境光线变化时轻松触发动作。当环境光线改变时,我们可以控制 RGB LED 的亮度。我们使用模拟输入来确定环境光线水平,并使用 PWM 生成模拟输出以控制 RGB LED 的亮度级别。
使用 wiring-x86 库控制模拟输入
到目前为止,我们一直在使用mraa库来处理模拟输入并检索环境光线水平。然而,我们也在之前的示例中使用了wiring-x86库。我们只需更改几行面向对象的代码,就可以用wiring-x86库替换mraa库来读取模拟值。
首先,我们必须用与wiring-x86库兼容的版本替换AnalogLed类的代码。我们在第四章中创建了此版本,使用 RESTful API 和脉冲宽度调制,示例代码文件为iot_python_chapter_04_04.py。当我们获取AnalogLed类的代码时,我们也将获得Board类。
以下行显示了与wiring-x86库兼容而不是使用mraa的VoltageInput类的新版本。示例代码文件为iot_python_chapter_06_04.py。
from wiringx86 import GPIOGalileoGen2 as GPIO
class VoltageInput:
initial_analog_pin_number = 14
def __init__(self, analog_pin):
self.analog_pin = analog_pin
self.gpio = Board.gpio
self.gpio.pinMode(
analog_pin + self.__class__.initial_analog_pin_number,
self.gpio.ANALOG_INPUT)
@property
def voltage(self):
raw_value = self.gpio.analogRead(
self.analog_pin +
self.__class__.initial_analog_pin_number)
return raw_value / 1023.0 * 5.0
我们创建了一个新的VoltageInput类版本,该版本声明了一个initial_analog_pin_number类属性,并将其设置为14。wiring-x86库使用 Arduino 兼容的数字来引用模拟输入引脚或 ADC 引脚。因此,模拟输入引脚0被称为14,模拟输入引脚1被称为15,依此类推。由于我们不希望修改代码的其他部分,我们使用类属性来指定必须加到接收到的analog_pin值上以将其转换为wiring-x86模拟引脚编号的数字。
构造函数,即__init__方法,将Board.gpio类属性的引用保存到self.gpio中,并使用接收到的analog_pin和initial_analog_pin_number类属性中指定的值作为其pin参数,以及self.gpio.ANALOG_INPUT作为其mode参数调用其pinMode方法。这样,我们配置引脚为模拟输入引脚,将模拟输入引脚编号转换为wiring-x86兼容的模拟输入引脚编号。wiring-x86库在 GPIO 和模拟 I/O 引脚之间没有区别,我们可以通过Board.gpio类属性来管理它们。
所有的VoltageInput实例都将保存对创建GPIO类实例的同一Board.gpio类属性的引用,特别是具有debug参数设置为False的wiringx86.GPIOGalileoGen2类,以避免为低级通信提供不必要的调试信息。
该类定义了一个voltage属性,该属性调用 GPIO 实例(self.gpio)的analogRead方法以从模拟引脚获取原始值,并将其保存到raw_value变量中。self.analog_pin属性加上initial_analog_pin_number类属性中指定的值指定了analogRead方法调用中的pin值。然后,代码返回raw_value除以1023再乘以5的结果。这样,该属性返回电压值,该值是从analogRead函数返回的原始值转换而来的。
小贴士
不幸的是,wiring-x86库不支持模拟数字转换器的 12 位分辨率。该库使用固定的 10 位分辨率,因此我们只能检测到 1024 个不同的值(2¹⁰ = 1024),或 1024 个单位,其值范围从 0 到 1023(包含),其中 0 代表 0V,1023 代表 5V。因此,我们必须在voltage属性中将原始值除以1023而不是4095。
代码的其余部分与之前示例中使用的相同。不需要对DarknessSensor类、BoardInteraction类或主循环进行更改,因为它们将自动与新VoltageInput类一起工作,并且其构造函数或voltage属性的参数没有发生变化。
以下行将启动与wiring-x86库一起工作的示例的新版本:
python iot_python_chapter_06_04.py
小贴士
我们可以在之前示例中对光敏电阻上的入射光进行相同的更改,以检查我们是否可以使用wiring-x86库实现完全相同的结果。唯一的区别将是检索到的电压值的精度,因为我们在这个情况下使用的是模拟数字转换器的 10 位分辨率。
在本地存储中记录日志
Python 提供了一个强大且灵活的日志 API,由标准库模块提供。我们可以使用日志模块来跟踪在板上运行我们的物联网应用程序时发生的事件,并通过利用本地存储选项将它们保存到日志文件中。
现在,我们将对我们之前使用mraa库工作的示例的最后版本进行更改,以记录从环境光传感器读取的电压值。我们只想在环境光发生变化时记录新的电压值,即当BoardInteraction.darkness_sensor.ambient_light的值发生变化时。我们将使用之前的代码作为基准来添加新的日志功能。示例的代码文件为iot_python_chapter_06_03.py。
我们将替换__main__方法。以下行展示了添加了日志功能的新版本。新的代码行被突出显示,示例的代码文件为iot_python_chapter_06_05.py。
import logging
if __name__ == "__main__":
logging.basicConfig(
filename="iot_python_chapter_06_05.log",
level=logging.INFO,
format="%(asctime)s %(message)s",
datefmt="%m/%d/%Y %I:%M:%S %p")
logging.info("Application started")
last_ambient_light = ""
last_voltage = 0.0
while True:
BoardInteraction.darkness_sensor.measure_light()
new_ambient_light = BoardInteraction.darkness_sensor.ambient_light
if new_ambient_light != last_ambient_light:
# The ambient light value changed
logging.info(
"Ambient light value changed from {0} to {1}".format(
last_voltage, BoardInteraction.darkness_sensor.voltage))
last_ambient_light = new_ambient_light
last_voltage = BoardInteraction.darkness_sensor.voltage
print("Darkness level: {0}".format(new_ambient_light))
BoardInteraction.update_leds_brightness()
# Sleep 2 seconds
time.sleep(2)
第一行调用logging.basicConfig方法来对日志系统进行基本配置。fileName参数指定了我们要用于日志记录的文件名"iot_python_chapter_06_05.log"。由于我们没有为fileMode参数指定值,因此使用默认的'a'模式,连续运行的消息将被追加到指定的日志文件名,即文件不会被覆盖。
小贴士
我们在fileName参数中未指定任何路径,因此,日志文件将在 Python 脚本运行的同一文件夹中创建,即/home/root文件夹。在这种情况下,日志文件将使用启动 Yocto Linux 分发的 microSD 卡上的可用存储空间。
format参数指定了"%(asctime)s %(message)s",因为我们希望存储日期和时间,然后是消息。datefmt参数指定了"%m/%d/%Y %I:%M:%S %p"作为我们希望用于包含在所有附加到日志的行前面的日期和时间格式的日期和时间。我们想要一个简短的日期(月/日/年),后面跟着一个简短的时间(小时/分钟/秒 AM/PM)。我们只想将信息日志记录到文件中,因此,level参数指定了logging.INFO,将根日志记录器级别设置为该值。
下一行调用logging.info方法来记录第一个事件:开始执行的应用程序。在进入循环之前,代码声明了一个新的last_voltage局部变量并将其初始化为0.0。我们希望在环境光改变时记录前一次电压和新的电压值,因此,将最后一次电压保存在新变量中是必要的。当环境光改变时,对logging.info方法的调用将记录从前一次电压到新电压值的转换。然而,非常重要的一点是要注意,当此方法第一次被调用时,前一次电压将等于0.0。下一行将BoardInteraction.darkness_sensor.voltage的值保存到last_voltage变量中。
以下行将启动新版本的示例,该示例将创建iot_python_chapter_06_05.log文件:
python iot_python_chapter_06_05.py
让 Python 脚本运行几分钟,并对光敏电阻上的入射光进行多次更改。这样,你将在日志文件中生成许多行。然后,你可以使用你喜欢的 SFTP 客户端从/home/root下载日志文件并阅读它。
以下行显示了在执行应用程序后生成的日志文件中的某些示例行:
03/08/2016 04:54:46 PM Application started
03/08/2016 04:54:46 PM Ambient light value changed from 0.0 to 4.01953601954
03/08/2016 04:55:20 PM Ambient light value changed from 4.01953601954 to 3.91208791209
03/08/2016 04:55:26 PM Ambient light value changed from 3.91208791209 to 2.49572649573
03/08/2016 04:55:30 PM Ambient light value changed from 2.49572649573 to 3.40903540904
03/08/2016 04:55:34 PM Ambient light value changed from 3.40903540904 to 2.19291819292
03/08/2016 04:55:38 PM Ambient light value changed from 2.19291819292 to 3.83394383394
03/08/2016 04:55:42 PM Ambient light value changed from 3.83394383394 to 4.0
03/08/2016 04:55:48 PM Ambient light value changed from 4.0 to 3.40903540904
03/08/2016 04:55:50 PM Ambient light value changed from 3.40903540904 to 2.89133089133
03/08/2016 04:55:56 PM Ambient light value changed from 2.89133089133 to 3.88278388278
03/08/2016 04:55:58 PM Ambient light value changed from 3.88278388278 to 4.69841269841
03/08/2016 04:56:00 PM Ambient light value changed from 4.69841269841 to 3.93650793651
处理 USB 附加存储
记录与传感器相关事件的日志文件可能会迅速增长,因此,将日志文件存储在 microSD 存储空间可能会成为一个问题。我们可以处理高达 32 GB 的 microSD 卡。因此,一个选项是在更大的 microSD 卡上创建 Yocto Linux 镜像,并继续使用单个存储空间。这将需要我们从默认镜像中扩展分区。另一个选项是利用云服务,只需在我们的本地存储中保留受限制的日志。然而,我们将在稍后处理这个选项。现在,我们想要探索我们使用本地存储的附加选项。
如我们在第一章中学习到的,理解和设置基础物联网硬件,英特尔 Galileo Gen 2 板提供了一个标记为USB HOST的 USB 2.0 主机连接器。我们可以使用此连接器插入 USB 闪存驱动器以进行额外存储,并将日志文件保存到新存储中。
在插入任何 USB 闪存驱动器之前,请在 SSH 终端中运行以下命令以列出分区表:
fdisk -l
以下行显示了由上一个命令生成的输出示例。您的输出可能不同,因为它取决于您用于引导 Yocto Linux 的微 SD 卡。请注意,/dev/mmcblk0磁盘标识了微 SD 卡,并且您有两个分区:/dev/mmcblk0p1和/dev/mmcblk0p2。
Disk /dev/mmcblk0: 7.2 GiB, 7746879488 bytes, 15130624 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x000a69e4
Device Boot Start End Blocks Id System
/dev/mmcblk0p1 * 2048 106495 52224 83 Linux
/dev/mmcblk0p2 106496 2768895 1331200 83 Linux
现在,我们将把一个 USB 闪存驱动器插入到板载的 USB 2.0 主机连接器,我们将运行必要的命令来挂载它,然后我们将修改代码以将日志保存到 USB 闪存驱动器内的一个文件夹中。您需要一个与 USB 2.0 兼容的预格式化 USB 闪存驱动器来运行此示例。
以下图片显示了连接到板载 USB 2.0 主机连接器的 USB 闪存驱动器,标记为USB HOST。插入 USB 闪存驱动器后,请等待几秒钟。

Yocto Linux 将在/dev文件夹中添加一个新的块设备。在 SSH 终端中运行以下命令以列出分区表:
fdisk -l
以下行显示了由上一个命令生成的输出示例。您的输出可能不同,因为它取决于您使用的 USB 驱动器和微 SD 卡。将输出与您在插入 USB 闪存驱动器之前执行相同命令时生成的输出进行比较。附加的行提供了有关 USB 闪存驱动器、其磁盘名称和其分区的信息。突出显示的行显示了 USB 分区的详细信息,标识为/dev/sda磁盘和 FAT32 分区/dev/sda1。我们将使用此分区名称作为我们下一步操作之一。
Disk /dev/mmcblk0: 7.2 GiB, 7746879488 bytes, 15130624 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x000a69e4
Device Boot Start End Blocks Id System
/dev/mmcblk0p1 * 2048 106495 52224 83 Linux
/dev/mmcblk0p2 106496 2768895 1331200 83 Linux
Disk /dev/sda: 3.8 GiB, 4026531840 bytes, 7864320 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x02bb0a1a
Device Boot Start End Blocks Id System
/dev/sda1 * 64 7864319 3932128 b W95 FAT32
现在,有必要创建一个挂载点。我们必须在/media文件夹中创建一个新的子文件夹。我们将使用usb作为子文件夹的名称,因此我们将挂载驱动器的文件夹将是/media/usb。运行以下命令以创建文件夹:
mkdir /media/usb
运行以下命令在最近创建的/media/usb文件夹中挂载分区。在之前的步骤中,我们检索了分区名称,它被命名为/dev/sda1。您的分区名称可能不同,因此您只需将/dev/sda1替换为您在执行列出磁盘及其分区的 fdisk 命令时列出的分区名称即可。
mount /dev/sda1 /media/usb
现在,我们可以通过/media/usb文件夹访问 USB 闪存驱动器的内容,也就是说,无论何时我们在该文件夹中创建文件夹或文件,我们都是在向 USB 闪存驱动器分区写入。
运行以下命令以创建一个新的/media/usb/log文件夹,我们将在此文件夹中存储我们的物联网应用的日志:
mkdir /media/usb/log
现在,我们将更改在__main__方法中调用logging.basicConfig方法时传递给文件名参数的值。我们想在/media/usb/log文件夹中保存日志文件。这样,我们将它存储在 USB 闪存驱动器的log文件夹中。我们将使用之前的代码作为基准来更改日志文件名及其路径。示例代码文件是iot_python_chapter_06_05.py。
以下行显示了调用logging.basicConfig方法的新代码和示例代码文件iot_python_chapter_06_06.py。其余的代码与我们在前面的示例中使用的一样。
import logging
if __name__ == "__main__":
logging.basicConfig(
filename="/media/usb/log/iot_python_chapter_06_06.log",
level=logging.INFO,
format="%(asctime)s %(message)s",
datefmt="%m/%d/%Y %I:%M:%S %p")
以下行将启动新版本的示例,该示例将在/media/usb/log文件夹中创建iot_python_chapter_06_06.log文件:
python iot_python_chapter_06_06.py
保持 Python 脚本运行几分钟,并对光敏电阻上的环境光进行多次更改。这样,你将在日志文件中生成多行。然后,你可以使用你喜欢的 SFTP 客户端从/media/usb/log下载日志文件并阅读它。然而,别忘了在你的 SFTP 客户端中返回到home/root文件夹,因为这是上传 Python 脚本的那个文件夹。
如果你需要将 USB 闪存驱动器拔出以连接到另一台计算机或设备,首先你必须中断 Python 脚本的执行,然后,你必须运行以下命令来卸载分区。在前面的步骤中,我们检索了分区名称,它被命名为/dev/sda1。你的分区名称可能不同,因此,你只需将/dev/sda1替换为你执行列出磁盘及其分区的fdisk命令时列出的分区名称。请小心,并确保你在运行 Yocto Linux 上的 shell 的终端上运行此命令。在执行之前,请确保你看到root@galileo:~#提示符。如果你在运行 Linux 或 OS X 的计算机上运行此命令,你可能会卸载你的一个驱动器。
umount /dev/sda1
现在,你可以从 USB 2.0 主机连接器中拔出 USB 闪存驱动器。
测试你的知识
-
Intel Galileo Gen 2 板为模拟数字转换器提供以下分辨率:
-
32 位。
-
64 位。
-
12 位。
-
-
模拟引脚允许我们检测的最大值:
-
4096 个不同的值,值的范围从 0 到 4095(包含)。
-
16384 个不同的值,值的范围从 0 到 16383(包含)。
-
256 个不同的值,值的范围从 0 到 255(包含)。
-
-
我们可以通过调用
mraa.Aio实例的以下方法来配置我们想要使用的位数作为分辨率:-
setADCResolution。 -
setBit。 -
setResolutionBits。
-
-
对
mraa.Aio实例的read方法的调用返回:-
基于为实例配置的分辨率位数的一个原始单位数。
-
一个从原始单位数自动转换的电压值。
-
一个以欧姆(Ω)为单位的电阻值。
-
-
我们可以使用模拟引脚来读取:
-
电阻值。
-
电流值。
-
电压值。
-
摘要
在本章中,我们学习了如何使用模拟输入来测量电压值。我们理解了模拟数字转换器不同位分辨率的影响,并编写了将读取的原始单位转换为电压值的代码。
我们使用模拟引脚和 mraa 以及 wiring-x86 库来测量电压。我们能够将可变电阻转换成电压源,并使其能够通过模拟输入、光敏电阻和分压器来测量黑暗程度。
与前几章一样,我们继续利用 Python 的面向对象特性,并创建了类来封装电压输入、光敏传感器以及必要的配置,使用 mraa 和 wiring-x86 库。我们的代码易于阅读和理解,并且我们可以轻松切换底层低级库。
当环境光线变化时,我们触发了动作,并且能够同时处理模拟输入和模拟输出。最后,我们通过利用 Python 标准库中包含的日志功能来注册事件。我们还学会了利用英特尔 Galileo Gen 2 板中包含的 USB 2.0 主机连接器来插入 USB 闪存驱动器并将其用作额外的存储。
现在我们能够以不同的方式和配置读取模拟输入,这使得我们的物联网设备能够读取由环境变化产生的模拟值,我们可以与更多种类的传感器一起工作,从现实世界获取数据,这是下一章的主题。
第七章. 使用传感器从现实世界获取数据
在本章中,我们将与各种传感器一起工作,以从现实世界获取数据。我们将涵盖以下主题:
-
理解传感器及其连接类型
-
学习在选择传感器时必须考虑的最重要事项
-
利用
upm库与多种不同的传感器一起工作 -
使用加速度计测量适当的加速度或 g 力的幅度和方向
-
使用三轴模拟加速度计
-
使用与 I²C 总线兼容的数字加速度计
-
使用
mraa库和 I²C 总线控制数字加速度计 -
使用模拟传感器测量环境温度
-
使用与 I²C 总线兼容的数字温度和湿度传感器
理解传感器及其连接类型
在第六章,使用模拟输入和本地存储,我们使用了一个包含在分压器中的光敏电阻,并将其连接到模拟输入引脚。我们能够测量环境光,并确定不同的暗度级别,并改变 RGB LED 的亮度级别。光敏电阻,也称为LDR(Light-Dependent Resistor的缩写)或光电池,是一种传感器。我们只需要将其包含在分压器中,就可以通过环境光改变光敏电阻的电阻值。这些电阻值的变动将在我们的模拟引脚中产生电压值的变动。因此,我们与一个电子组件配置一起工作,该配置生成一个模拟传感器,能够将环境光的变动转换为电压值。
有大量的传感器使我们能够从现实世界获取数据并将其转换为我们可以通过 Intel Galileo Gen 2 板上的不同通信端口收集的模拟或数字值,并用 Python 和不同的库进行处理。当我们使用光敏电阻来测量环境光时,我们将配置连接到模拟引脚,并使用mraa库然后是wiring-x86库来利用模数转换器来获取值。
在第二章,在 Intel Galileo Gen 2 上使用 Python,我们安装了最新可用的upm库。这个库为传感器和执行器提供了高级接口。每次我们与传感器一起工作时,通常都方便检查upm库是否支持它,因为高级接口可以为我们节省大量时间,并使我们从传感器获取值以及进行必要的单位转换变得更加容易。
在本章中,我们将利用具有许多不同传感器的upm库。然而,我们必须考虑到,有时upm库中为特定传感器提供的功能可能不足以满足需求,我们可能需要编写自己的底层代码,使用mraa或wiring-x86库与传感器进行交互。正如我们稍后将要分析的,根据连接类型,当传感器在upm库中不受支持时,只有mraa会为我们提供所有必要的功能。
显然,在选择传感器时,我们必须首先考虑我们想要测量的内容,例如温度。然而,这并不是我们选择特定传感器时唯一需要考虑的因素。当我们选择传感器时,我们必须考虑它们的功能、测量范围、精度以及连接类型等众多因素。以下列表列举了我们必须考虑的最重要的因素及其解释:
-
与英特尔 Galileo Gen 2 板和我们所使用的电压供应(5V 或 3.3V)的兼容性:有时,我们必须将多个传感器连接到板上,因此检查我们选择的传感器是否都能与板上的电压配置兼容是很重要的。一些传感器只有在我们有特定设置的情况下才能与板一起工作。
-
功耗:我们必须考虑到一些传感器具有不同的工作模式。例如,一些传感器具有高性能模式,需要比正常模式更多的电力。由于我们可能会将多个传感器连接到板上,因此考虑所有传感器连接到板上并使用我们将在其中使用的模式时的整体功耗也很重要。此外,一些传感器在我们不使用它们时,会切换到省电模式。
-
连接类型:为了决定最方便的连接类型,我们需要回答几个问题。我们是否有必要的连接、通信或接口端口?它们是否可用?连接类型和我们需要的距离是否会影响测量值的准确性?此外,当我们为我们的板选择第一个传感器时,所有连接可能都是可用的,但随着我们添加更多传感器,情况会发生变化,这可能会迫使选择具有不同连接类型的传感器。让我们考虑以下情况,我们已经在 6 个不同的位置测量环境光线。我们有 6 个光电电阻,通过 6 个分压器配置连接,并连接到 6 个可用的模拟输入引脚,因此我们没有额外的模拟引脚可用。如果我们必须添加温度传感器,我们不能添加需要模拟输入引脚的模拟传感器,因为它们都已经连接到光线传感器。在这种情况下,我们必须使用可以连接到 I²C 或 SPI 总线的数字温度传感器。另一个选择是使用可以连接到 UART 端口的数字温度传感器。我们将在稍后深入探讨传感器的不同连接类型。
-
测量范围:传感器的规格表明了它们的测量范围。例如,测量环境温度的温度传感器可以有一个测量范围从-40ºF 到 185ºF(相当于-40ºC 到 85ºC)。如果我们需要测量可以达到 90ºC 的环境温度,我们必须选择一个具有更高上限范围的温度传感器。例如,另一个测量环境温度的传感器提供的测量范围是-40ºF 到 257ºF(相当于-40ºC 到 125ºC),将适合这项工作。
-
灵敏度和精度:每个传感器都是灵敏的,可能提供不同的可配置精度级别。我们必须确保传感器提供的精度符合我们的需求。随着测量值的改变,考虑灵敏度,也称为测量分辨率,是很重要的。例如,如果我们必须测量温度,并且必须能够根据我们使用的单位确定至少 2ºF 或 1ºC 的变化,我们必须确保传感器能够提供所需灵敏度。
小贴士
当我们开始选择合适的传感器时,分析测量范围、灵敏度和精度时注意单位非常重要。一个典型的例子是温度传感器,它可以以摄氏度(ºC)或华氏度(ºF)来表示数值。
-
延迟: 确定我们能够等待传感器收集新值的时间以及它是否能够在这么短的时间内提供真实的新值非常重要。当我们在真实环境或测量对象中测量的测量值发生变化时,传感器需要一些时间才能提供新的测量值。有时,这些可能是微秒,但在其他情况下,它们可以是毫秒甚至秒。这取决于传感器,我们在选择适合我们项目的传感器时必须考虑这一点。例如,我们可能需要一个温度传感器,每秒允许我们测量 2 个温度值,因此,我们必须与延迟低于 500 毫秒(0.5 秒)的传感器合作以达到我们的目标。具体来说,我们可以选择延迟为 200 毫秒的温度传感器。不幸的是,有时我们必须深入研究数据表来检查某些传感器及其使用的电子组件的延迟值。
-
工作范围和特殊环境要求: 考虑传感器的工作范围非常重要。有时,传感器必须在工作于特定环境条件下,而这些条件可能不适合所有可用的传感器。以下是一些粗略的环境要求示例:高抗冲击性、防水、极高温度和非常高的湿度水平。
-
尺寸: 传感器具有不同的尺寸。有时只有特定的尺寸适合我们的项目。
-
协议、upm 库的支持和 Python 绑定: 我们最终将使用 Python 代码处理从传感器获取的数据,因此,确保我们可以在 Python 中使用传感器非常重要。在某些情况下,我们不希望编写底层代码,并确保传感器在
upm库中得到支持。在其他情况下,我们必须确保我们有必要的 Python 库来处理某些数字传感器使用的协议。例如,许多使用 UART 端口的温度传感器使用 MODBUS 串行通信协议。如果它们在upm库中没有得到支持,我们必须使用特定的 Python 库来通过 MODBUS 串行通信协议建立通信,这可能需要我们在没有先前使用此协议的经验的情况下进行额外的工作。 -
成本: 显然,我们必须考虑传感器的成本。可能符合我们所有要求的最佳传感器非常昂贵,我们可能决定使用功能较少或精度较低但成本较低的另一种传感器。我们有大量具有令人印象深刻的功能且与英特尔 Galileo Gen 2 板兼容的廉价传感器。然而,我们始终必须考虑每个传感器的成本,以便根据我们的需求和预算进行选择。
我们可以将传感器或模块连接到英特尔 Galileo Gen 2 板上,可以使用以下连接类型。列表列出了制造商通常用来描述模块连接类型的缩写及其解释:
-
AIO:该模块需要一个或多个模拟输入引脚。需要模拟输入引脚的传感器被称为模拟传感器。
-
GPIO:该模块需要一个或多个 GPIO 引脚。
-
I²C:该模块需要两根线连接到两个 I²C 总线线:SCL(代表 Serial CLock)和 SDA(代表 Serial DAta)。只要每个设备都有一个不同的 I²C 地址,我们就可以将许多设备连接到这个总线。
-
SPI:该模块需要三根线连接到三个 SPI 总线线:MISO(代表 Master In Slave Out)、MOSI(代表 Master Out Slave In)和 SCK(代表 Serial Clock)。
-
UART:该模块使用串行连接(RX/TX),因此需要两根线连接到 UART 端口的两个引脚:TX->1 和 RX<-0。UART 端口代表 通用异步接收/发送器。
与 I²C 总线、SPI 总线或 UART 端口一起工作的模块被称为 数字传感器,因为它们使用数字接口。一些模块将其中一个总线或 UART 端口与 GPIO 引脚结合使用。
我们已经使用 mraa 和 wiring-x86 库处理了模拟输入和模拟数字转换器。我们还使用这些库处理了配置为输入引脚的 GPIO 引脚。然而,我们还没有处理 I²C 总线、SPI 总线或 UART 端口。
mraa 库提供了以下类,允许我们与之前提到的串行总线和 UART 端口一起工作:
-
mraa.I2c:该类表示一个 I²C 总线主设备(板),可以通过选择它们的地址与多个 I²C 总线从设备进行通信。我们可以创建许多此类实例来与多个从设备进行交互。该类允许我们将数据写入和从连接到 I²C 总线的从设备读取数据。 -
Mraa.Spi:该类表示 SPI 总线和其芯片选择。该类允许我们将数据写入和从连接到 SPI 总线的设备读取数据。 -
mraa.UART:该类表示 UART 端口,并允许我们配置、向 UART 端口发送数据并从 UART 端口接收数据。
提示
我们可以使用之前解释过的由 mraa 库提供的类来与任何数字模块进行交互。然而,这需要我们花一些时间阅读模块的数据表,了解它们的工作模式,编写代码将数据写入和从适当的总线或 UART 端口读取。每个模块都有自己的 API,我们必须通过串行总线或 UART 端口来组合请求和处理响应。
首先,我们将利用每个模块的upm库。在少数情况下,我们还将使用mraa库中的适当类来了解如何使用低级接口与传感器交互。这样,如果我们必须与upm库不支持模块一起工作,我们可以分析数据表提供的信息,并编写代码与模块交互。
与加速度计一起工作
加速度计使我们能够测量加速度或 g 力的幅度和方向。平板电脑和智能手机使用加速度计根据我们握持设备的方向自动在纵向和横向模式之间切换。此外,内置的加速度计允许我们通过在设备的不同方向上用不同强度的微小动作来控制应用程序。
加速度计使我们能够通过测量由于重力产生的加速度来检测物体相对于地球表面的方向。此外,当我们想要检测物体开始或停止移动时,加速度计非常有用。加速度计还能够检测振动和物体下落。
小贴士
加速度计通常以 g 力为单位测量加速度,缩写为g。重要的是要避免由单位名称中包含的力这个词引起的混淆,因为我们测量的是加速度而不是力。一些加速度计使用每秒平方米(m/s²)作为它们的测量单位而不是 g 力。
现在,大多数加速度计能够测量三个轴的加速度,被称为三轴加速度计或三轴加速度计。一个三轴加速度计可以测量x、y和z轴的加速度。如果我们想测量小的加速度或振动,使用小范围的三轴加速度计会更方便,因为它们提供了必要的灵敏度。
将模拟加速度计连接到模拟输入引脚
理解加速度计工作原理的最简单方法是在一个简单的例子中使用它。现在,我们将使用一个具有从-3g 到+3g 的全量程传感范围的模拟三轴加速度计。这种加速度计需要三个模拟输入引脚,每个测量轴一个。加速度计根据每个轴测量的加速度提供电压级别。
我们将使用标记为A0、A1和A2的三个模拟引脚来连接模拟加速度计断开板的正电压输出。完成必要的布线后,我们将编写 Python 代码来测量和显示三个轴(x、y 和 z)的加速度。这样,我们将读取将模拟值转换为其数字表示的结果,并将其映射到加速度值。
我们需要一个 SparkFun 三轴加速度计扩展板 ADXL335 来与这个示例一起使用。以下 URL 提供了有关此扩展板的详细信息:www.sparkfun.com/products/9269。该扩展板集成了来自 Analog Devices 的 ADXL335 加速度计传感器。
提示
供应给扩展板的电源应在 1.8VDC 至 3.6VDC 之间,因此我们将使用标记为3V3的电源引脚作为电源,以确保我们提供 3.3V,并且我们永远不会向扩展板提供5V。
还可以使用 Seeedstudio Grove 3 轴模拟加速度计与这个示例一起工作。以下 URL 提供了有关此模块的详细信息:www.seeedstudio.com/depot/Grove-3Axis-Analog-Accelerometer-p-1086.html。如果您使用此模块,您可以使用标记为3V3或5V的电源引脚作为电源,因为扩展板能够与 3V 至 5V 的电压供应一起工作。全量程与 SparkFun 扩展板相同,并且两者使用相同的加速度计传感器。布线对两个模块都是兼容的。
以下图示显示了 SparkFun 三轴加速度计扩展板 ADXL335、必要的布线以及从 Intel Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件为iot_fritzing_chapter_07_01.fzz,以下图片是面包板视图:

以下图片显示了用电子元件表示的符号的原理图:

如前图所示,我们有以下连接:
-
标记为A0的模拟输入引脚连接到标记为X(在扩展板符号中为XOUT)的加速度计输出引脚
-
标记为A1的模拟输入引脚连接到标记为Y(在扩展板符号中为YOUT)的加速度计输出引脚
-
标记为A2的模拟输入引脚连接到标记为Z(在扩展板符号中为ZOUT)的加速度计输出引脚
-
标记为3V3的电源引脚连接到标记为VCC的加速度计电源引脚
-
标记为GND的接地引脚连接到标记为GND的加速度计接地引脚
现在,是时候进行所有必要的布线了。在添加或移除任何线从板上的引脚之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从 Intel Galileo Gen 2 板上拔掉电源供应。确保你使用粗线,这样你就可以在不意外拔掉电缆的情况下将加速度计扩展板移动到不同的方向。
使用模拟加速度计测量三轴加速度
upm 库在 pyupm_adxl335 模块中包括对三个轴模拟加速度计扩展板的支撑。在此模块中声明的 ADXL335 类代表连接到我们板上的三个轴模拟加速度计。该类使得校准加速度计并将从模拟输入读取的原始值转换为以 g 单位表示的值变得容易。
我们将创建一个新的 Accelerometer 类来表示加速度计,并使我们能够更容易地检索加速度值,而无需担心与 ADXL335 类的实例一起工作时必要的类型转换。我们将使用 ADXL335 类与加速度计交互。以下行显示了与 upm 库(特别是 pyupm_adxl335 模块)一起工作的新 Accelerometer 类的代码。示例代码文件为 iot_python_chapter_07_01.py。
import pyupm_adxl335 as upmAdxl335
import time
class Accelerometer:
def __init__(self, pinX, pinY, pinZ):
self.accelerometer = upmAdxl335.ADXL335(
pinX, pinY, pinZ)
self.accelerometer.calibrate()
self.x_acceleration_fp = upmAdxl335.new_floatPointer()
self.y_acceleration_fp = upmAdxl335.new_floatPointer()
self.z_acceleration_fp = upmAdxl335.new_floatPointer()
self.x_acceleration = 0.0
self.y_acceleration = 0.0
self.z_acceleration = 0.0
def calibrate(self):
self.accelerometer.calibrate()
def measure_acceleration(self):
# Retrieve the acceleration values for the three axis
self.accelerometer.acceleration(
self.x_acceleration_fp,
self.y_acceleration_fp,
self.z_acceleration_fp)
self.x_acceleration = upmAdxl335.floatPointer_value(
self.x_acceleration_fp)
self.y_acceleration = upmAdxl335.floatPointer_value(
self.y_acceleration_fp)
self.z_acceleration = upmAdxl335.floatPointer_value(
self.z_acceleration_fp)
在创建 Accelerometer 类的实例时,我们必须指定每个轴引脚连接到的模拟引脚编号,所需的 pinX、pinY 和 pinZ 参数。构造函数,即 __init__ 方法,使用接收到的 pinX、pinY 和 pinZ 参数创建一个新的 upmAdxl335.ADXL335 实例,并将其引用保存到 accelerometer 属性中。
upmAdxl335.ADXL335 实例需要使用浮点指针来检索三个轴的加速度值。因此,构造函数通过调用 upmAdxl335.new_floatPointer() 将三个 float * 类型(浮点指针)的对象保存到以下三个属性中。
-
x_acceleration_fp -
y_acceleration_fp -
z_acceleration_fp
最后,构造函数创建并初始化三个属性为 0.0:x_acceleration、y_acceleration 和 z_acceleration。构造函数执行后,我们必须校准加速度计,然后,我们将准备好检索三个轴的加速度值:x、y 和 z。
该类定义了以下两个方法:
-
calibrate:调用self.accelerometer的校准方法来校准模拟加速度计。 -
measure_acceleration:检索三个轴的加速度值并将它们保存到以下三个属性中:x_acceleration、y_acceleration和z_acceleration。加速度值以重力加速度(g)表示。首先,代码调用self.accelerometer的acceleration方法,并将三个float *类型的对象作为参数。该方法读取从三个模拟引脚获取的原始值,将它们转换为适当的重力加速度(g)值,并使用更新后的值更改接收到的float*类型对象的浮点值。然后,代码调用upmAdxl335.floatPointer_value方法从float*类型的对象中检索浮点值,并更新三个属性:x_acceleration、y_acceleration和z_acceleration。
现在,我们将编写一个循环,该循环将运行校准,每 500 毫秒检索并显示三个轴的加速度值(以 g 力表示),即每秒两次。示例的代码文件为iot_python_chapter_07_01.py。
if __name__ == "__main__":
# The accelerometer is connected to analog pins A0, A1 and A2
# A0 -> x
# A1 -> y
# A2 -> z
accelerometer = Accelerometer(0, 1, 2)
# Calibrate the accelerometer
accelerometer.calibrate()
while True:
accelerometer.measure_acceleration()
print("Acceleration for x: {0}g".format(accelerometer.x_acceleration))
print("Acceleration for y: {0}g".format(accelerometer.y_acceleration))
print("Acceleration for z: {0}g".format(accelerometer.z_acceleration))
# Sleep 0.5 seconds (500 milliseconds)
time.sleep(0.5)
第一行创建了一个之前编写的Accelerometer类的实例,其中pinX、pinY和pinZ的值分别为0、1和2。这样,实例将从标有A0、A1和A2的引脚读取模拟值。然后,代码调用Accelerometer实例的calibrate方法来校准模拟加速度计。
小贴士
校准测量传感器静止时的x、y和z轴值,然后,传感器使用这些值作为零值,即作为基线。此模拟传感器的默认灵敏度是 0.25V/g。
然后,代码将无限循环运行,即直到你通过按Ctrl + C或停止按钮中断执行,如果你使用具有远程开发功能的 Python IDE 来在板上运行代码。循环调用measure_acceleration方法来更新加速度值,然后以 g 力(g)的形式打印它们。
以下行将启动示例。别忘了你需要使用 SFTP 客户端将 Python 源代码文件传输到 Yocto Linux。在开始示例之前,请确保加速度计扩展板位于稳定且不震动的表面上。这样,校准才能正常工作。
python iot_python_chapter_07_01.py
运行示例后,执行以下操作:
-
以不同的方向对加速度计扩展板进行小幅度移动
-
在特定方向上对加速度计扩展板进行大幅度移动
-
将加速度计扩展板放置在稳定且不震动的表面上
由于之前的操作,你将看到三个轴测量的不同加速度值。以下行显示了当我们对扩展板进行大幅度移动时生成的某些示例输出行:
Acceleration for x: 0.0g
Acceleration for y: 0.4296875g
Acceleration for z: 0.0g
Acceleration for x: 0.0g
Acceleration for y: 0.52734375g
Acceleration for z: 0.0g
Acceleration for x: 0.0g
Acceleration for y: 0.60546875g
Acceleration for z: 0.0g
Acceleration for x: 0.01953125g
Acceleration for y: 0.68359375g
Acceleration for z: 0.0g
将数字加速度计连接到 I²C 总线
数字加速度计通常比模拟加速度计提供更好的精度、更高的分辨率和更高的灵敏度。现在,我们将使用一个从-16g 到+16g 的全量程数字 3 轴加速度计。我们将使用一个使用 I²C 总线来允许板与加速度计通信的扩展板。
我们将使用标有SDA和SCL的两个引脚将 I²C 总线的数据线和时钟线连接到数字加速度计扩展板上的相应引脚。完成必要的布线后,我们将编写 Python 代码来测量和显示三个轴(x、y和z)的加速度。这样,我们将读取通过 I²C 总线发送到加速度计的命令的结果,读取响应并将它们解码为以 g 力(g)表示的适当的加速度值。
我们需要 SparkFun 三轴加速度计扩展板 ADXL345 来与这个示例一起工作。以下网址提供了关于此扩展板的详细信息:www.sparkfun.com/products/9836。该扩展板集成了来自 Analog Devices 的 ADXL345 数字加速度计传感器,并为 SPI 和 I²C 总线提供支持。在这种情况下,我们将仅使用 I²C 总线。
小贴士
供应给扩展板的电源应在 2.0VDC 至 3.6VDC 之间,因此,我们必须使用标有3V3的电源引脚作为电源,以确保我们提供 3.3V,并且我们永远不会向扩展板提供5V。
也可以使用 Seeedstudio Grove 3 轴数字加速度计与这个示例一起工作。以下网址提供了关于此模块的详细信息:www.seeedstudio.com/depot/Grove-3Axis-Digital-Accelerometer16g-p-1156.html。如果您使用此模块,可以使用标有3V3或5V的电源引脚作为电源,因为扩展板能够与 3V 至 5V 的电压供应一起工作。全量程与 SparkFun 扩展板相同,并且两者都使用相同的加速度计传感器。这两款模块的布线是兼容的。
小贴士
Seeedstudio Grove 3 轴数字加速度计已准备好使用电缆插入 Grove 底板。Grove 底板是一块可以插入您的 Intel Galileo Gen 2 板的板子,它提供了数字、模拟和 I²C 端口,您可以使用适当的电缆轻松地将 Grove 传感器连接到底层的 Intel Galileo Gen 2 板。在我们的示例中,我们不会使用 Grove 底板,我们将继续使用布线连接每个不同的传感器。但是,如果您决定使用 Grove 底板与 Grove 传感器结合使用,您将获得相同的结果。我们将在下一个示例中使用的其他 Grove 传感器也将准备好与 Grove 底板一起工作。Grove 底板的最新版本是 V2,您可以在以下网址中获取更多关于它的信息:www.seeedstudio.com/depot/Base-Shield-V2-p-1378.html
以下图示展示了 Seeedstudio Grove 3 轴数字加速度计扩展板 ADXL345,必要的布线以及从 Intel Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件为iot_fritzing_chapter_07_02.fzz,以下图片是面包板视图。

以下图片显示了用符号表示电子组件的原理图。

如前图所示,我们有以下连接:
-
SDA引脚连接到标有SDA的加速度计引脚。这样,我们将数字加速度计连接到 I²C 总线的串行数据线。Intel Galileo Gen 2 板上的SDA引脚连接到标有A4的模拟输入引脚,因此,板上的符号使用A4/SDA标签。标有SDA的引脚与标有A4的引脚位于不同的位置,但它们在内部是连接的。
-
SCL引脚连接到标有SCL的加速度计引脚。这样,我们将数字加速度计连接到 I²C 总线的串行时钟线。Intel Galileo Gen 2 板上的SCL引脚连接到标有A5的模拟输入引脚,因此,板上的符号使用A5/SCL标签。标有SCL的引脚与标有A5的引脚位于不同的位置,但它们在内部是连接的。
-
标有5V的电源引脚连接到标有VCC的加速度计电源引脚。如果你使用 SparkFun 三轴加速度计扩展板 ADXL345,标有3V3的电源引脚连接到加速度计的电源引脚VCC。
-
标有GND的接地引脚连接到标有GND的加速度计接地引脚。
现在,是时候进行所有必要的接线了。不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并在从板上的引脚添加或移除任何电线之前,从 Intel Galileo Gen 2 板上拔掉电源。就像你使用模拟加速度计时做的那样,确保你使用粗线,这样你就可以在不意外拔掉电缆的情况下将加速度计扩展板移动到不同的方向。
使用数字加速度计测量三个轴的加速度
upm库在pyupm_adxl345模块中包括了对三个轴数字加速度计扩展板 ADXL345 的支持。在此模块中声明的Adxl345类代表基于 ADXL345 传感器的三个轴数字加速度计,连接到我们的板上。该类使得初始化传感器、通过 I²C 总线更新和检索三个轴的加速度值变得容易。该类在幕后与mraa.I²C类一起工作,与传感器通信,即向 ADXL345 传感器写入数据并从中读取数据,该传感器作为连接到 I²C 总线的从设备。
小贴士
不幸的是,upm库中的每个模块都不遵循我们应期望的 Python 代码的相同命名约定。例如,在我们之前的例子中,那个类名是ADXL335,使用大写字母,而在这个例子中,类名是Adxl345。
我们将创建Accelerometer类的新版本来表示加速度计,并使我们能够更容易地检索加速度值,而无需在处理Adxl345类的实例时担心特定的方法和数组。我们将使用Adxl345类与加速度计交互。以下行显示了与upm库一起工作的新Accelerometer类的代码,特别是与pyupm_adxl345模块一起工作。示例代码文件为iot_python_chapter_07_02.py。
import pyupm_adxl345 as upmAdxl345
import time
class Accelerometer:
def __init__(self, bus):
self.accelerometer = upmAdxl345.Adxl345(bus)
self.x_acceleration = 0.0
self.y_acceleration = 0.0
self.z_acceleration = 0.0
def measure_acceleration(self):
# Update the acceleration values for the three axis
self.accelerometer.update()
# Retrieve the acceleration values for the three axis
acceleration_array = \
self.accelerometer.getAcceleration()
self.x_acceleration = acceleration_array[0]
self.y_acceleration = acceleration_array[1]
self.z_acceleration = acceleration_array[2]
在创建Accelerometer类的实例时,我们需要指定数字加速度计连接的 I²C 总线编号,这需要在bus必需参数中指定。构造函数,即__init__方法,使用接收到的bus参数创建一个新的upmAdxl345.Adxl345实例,并将其引用保存在accelerometer属性中。
upmAdxl345.Adxl345实例需要与三个轴的加速度值检索的浮点指针数组一起工作。我们希望使用易于理解的属性,因此构造函数创建并初始化了三个属性,其值为0.0:x_acceleration、y_acceleration和z_acceleration。构造函数执行后,我们有一个初始化的数字加速度计,可以检索三个轴的加速度值:x、y和z。
该类定义了一个measure_acceleration方法,该方法更新传感器中三个轴的加速度值,从传感器检索这些加速度值,并将其最终保存在以下三个属性中:x_acceleration、y_acceleration和z_acceleration。加速度值以 g 力(g)表示。
首先,measure_acceleration方法内的代码调用self.accelerometer的update方法,请求传感器更新读取值。然后,代码调用self.accelerometer的getAcceleration方法,检索三个轴的加速度值,并将返回的数组保存在局部变量acceleration_array中。数组中的第一个元素包含 x 轴的加速度值,第二个为 y 轴,第三个为 z 轴。因此,代码使用acceleration_array数组中的值更新以下三个属性:x_acceleration、y_acceleration和z_acceleration。这样,我们可以通过访问适当的属性而不是处理可能导致混淆的数组元素来轻松访问每个加速度值。
现在,我们将编写一个循环,该循环将每 500 毫秒运行一次校准,检索并显示三个轴的加速度值(以 g 力g表示),即每秒两次。示例代码文件为iot_python_chapter_07_02.py。
if __name__ == "__main__":
accelerometer = Accelerometer(0)
while True:
accelerometer.measure_acceleration()
print("Acceleration for x: {:5.2f}g".
format(accelerometer.x_acceleration))
print("Acceleration for y: {:5.2f}g".
format(accelerometer.y_acceleration))
print("Acceleration for z: {:5.2f}g".
format(accelerometer.z_acceleration))
# Sleep 0.5 seconds (500 milliseconds)
time.sleep(0.5)
第一行创建了一个之前编写的Accelerometer类的实例,其中bus参数的值为0。mraa.I2c类识别出我们用编号0连接加速度计的 I²C 总线。这样,该实例将通过 I²C 总线与数字加速度计建立通信。英特尔 Galileo Gen 2 板是总线上的主设备,而数字加速度计,以及连接到该总线的任何其他设备,都充当从设备。
然后,代码运行一个无限循环,调用measure_acceleration方法来更新加速度值,然后以 g 力(g)为单位打印它们。
以下行将开始示例:
python iot_python_chapter_07_02.py
在运行示例之后,执行与上一个示例中相同的操作。这些操作的结果,您将看到三个轴测量的不同加速度值。以下行展示了当我们用分线板进行小幅度移动时生成的某些示例输出行:
Acceleration for x: 0.000g
Acceleration for y: 0.056g
Acceleration for z: 0.000g
Acceleration for x: 0.000g
Acceleration for y: 0.088g
Acceleration for z: 0.000g
Acceleration for x: 0.000g
Acceleration for y: 0.872g
Acceleration for z: 0.056g
使用 mraa 库通过 I²C 总线控制数字加速度计
有时,upm库中针对特定传感器的功能可能不包括其所有可能的用法和配置。我们之前示例中使用的upmAdxl345.Adxl345类就是这种情况的一个例子。此类不允许我们配置加速度计所需的比例,而传感器支持以下四个可选测量范围:±2g、±4g、±8g 和±16g。如果我们想使用upm模块中未包含的特定功能,我们可以使用适当的mraa类与传感器交互,在这种情况下,我们可以使用mraa.I2c通过 I²C 总线控制数字加速度计。
我们将使用 upm 模块的 C++源代码作为基准来编写我们自己的 Python 代码,该代码通过mraa.I2c类通过 I²C 总线控制加速度计。C++源代码文件是adxl1345.cxx,可以在以下 GitHub URL 中找到:github.com/intel-iot-devkit/upm/blob/master/src/adxl345/adxl345.cxx。由于我们使用 C++源代码作为基准,我们将使用相同的命名约定(大写字母)来声明#define中的常量,但我们将它们转换为类属性。
以下行展示了用于与mraa.I2c类实例通信以与数字加速度计交互的新Adxl1345类的代码。示例的代码文件为iot_python_chapter_07_03.py。
class Adxl345:
# Read buffer length
READ_BUFFER_LENGTH = 6
# I2C address for the ADXL345 accelerometer
ADXL345_I2C_ADDR = 0x53
ADXL345_ID = 0x00
# Control registers
ADXL345_OFSX = 0x1E
ADXL345_OFSY = 0x1F
ADXL345_OFSZ = 0x20
ADXL345_TAP_THRESH = 0x1D
ADXL345_TAP_DUR = 0x21
ADXL345_TAP_LATENCY = 0x22
ADXL345_ACT_THRESH = 0x24
ADXL345_INACT_THRESH = 0x25
ADXL345_INACT_TIME = 0x26
ADXL345_INACT_ACT_CTL = 0x27
ADXL345_FALL_THRESH = 0x28
ADXL345_FALL_TIME = 0x29
ADXL345_TAP_AXES = 0x2A
ADXL345_ACT_TAP_STATUS = 0x2B
# Interrupt registers
ADXL345_INT_ENABLE = 0x2E
ADXL345_INT_MAP = 0x2F
ADXL345_INT_SOURCE = 0x30
# Data registers (read only)
ADXL345_XOUT_L = 0x32
ADXL345_XOUT_H = 0x33
ADXL345_YOUT_L = 0x34
ADXL345_YOUT_H = 0x35
ADXL345_ZOUT_L = 0x36
ADXL345_ZOUT_H = 0x37
DATA_REG_SIZE = 6
# Data and power management
ADXL345_BW_RATE = 0x2C
ADXL345_POWER_CTL = 0x2D
ADXL345_DATA_FORMAT = 0x31
ADXL345_FIFO_CTL = 0x38
ADXL345_FIFO_STATUS = 0x39
# Useful values
ADXL345_POWER_ON = 0x08
ADXL345_AUTO_SLP = 0x30
ADXL345_STANDBY = 0x00
# Scales and resolution
ADXL345_FULL_RES = 0x08
ADXL345_10BIT = 0x00
ADXL345_2G = 0x00
ADXL345_4G = 0x01
ADXL345_8G = 0x02
ADXL345_16G = 0x03
def __init__(self, bus):
# Init bus and reset chip
self.i2c = mraa.I2c(bus)
# Set the slave to talk to
if self.i2c.address(self.__class__.ADXL345_I2C_ADDR) != mraa.SUCCESS:
raise Exception("i2c.address() failed")
message = bytearray(
[self.__class__.ADXL345_POWER_CTL,
self.__class__.ADXL345_POWER_ON])
if self.i2c.write(message) != mraa.SUCCESS:
raise Exception("i2c.write() control register failed")
if self.i2c.address(self.__class__.ADXL345_I2C_ADDR) != mraa.SUCCESS:
raise Exception("i2c.address() failed")
message = bytearray(
[self.__class__.ADXL345_DATA_FORMAT,
self.__class__.ADXL345_16G | self.__class__.ADXL345_FULL_RES])
if self.i2c.write(message) != mraa.SUCCESS:
raise Exception("i2c.write() mode register failed")
# 2.5V sensitivity is 256 LSB/g = 0.00390625 g/bit
# 3.3V x and y sensitivity is 265 LSB/g = 0.003773584 g/bit, z is the same
self.x_offset = 0.003773584
self.y_offset = 0.003773584
self.z_offset = 0.00390625
self.x_acceleration = 0.0
self.y_acceleration = 0.0
self.z_acceleration = 0.0
self.update()
def update(self):
# Set the slave to talk to
self.i2c.address(self.__class__.ADXL345_I2C_ADDR)
self.i2c.writeByte(self.__class__.ADXL345_XOUT_L)
self.i2c.address(self.__class__.ADXL345_I2C_ADDR)
xyz_raw_acceleration = self.i2c.read(self.__class__.DATA_REG_SIZE)
x_raw_acceleration = (xyz_raw_acceleration[1] << 8) | xyz_raw_acceleration[0]
y_raw_acceleration = (xyz_raw_acceleration[3] << 8) | xyz_raw_acceleration[2]
z_raw_acceleration = (xyz_raw_acceleration[5] << 8) | xyz_raw_acceleration[4]
self.x_acceleration = x_raw_acceleration * self.x_offset
self.y_acceleration = y_raw_acceleration * self.y_offset
self.z_acceleration = z_raw_acceleration * self.z_offset
首先,该类声明了许多常量,这使得我们更容易理解通过 I²C 总线与加速度计交互的代码。例如,ADXL345_I2C_ADDR常量指定了 I²C 总线中 ADXL345 加速度计的地址,十六进制为 53(0x53)。如果我们只是在代码中看到0x53,我们不会理解它是传感器的 I²C 总线地址。我们导入了 C++版本中定义的所有常量,以便在需要添加初始版本中未包含的额外功能时,我们拥有所有必要的值。制造商提供的数据表提供了必要的细节,以了解每个寄存器的地址以及命令在 I²C 总线中的工作方式。
在创建Adxl345类的实例时,我们必须指定数字加速度计连接的 I²C 总线编号,该编号作为bus必需参数。构造函数,即__init__方法,使用接收到的bus参数创建一个新的mraa.I2c实例,并将其引用保存在i2c属性中。
self.i2c = mraa.I2c(bus)
在 I²C 总线中执行任何读取或写入操作之前,调用mraa.I2c实例的address方法以指示我们想要与之通信的从设备是一个好习惯。在这种情况下,从设备的地址由ADXL345_I2C_ADDR常量指定。
if self.i2c.address(self.__class__.ADXL345_I2C_ADDR) != mraa.SUCCESS:
raise Exception("i2c.address() failed")
然后,代码通过创建一个包含我们想要写入从设备的两个十六进制值的bytearray来构建一条消息:ADXL345_POWER_CTL和ADXL345_POWER_ON。我们可以将这条消息解读为“将开启写入电源控制寄存器”。使用此消息调用mraa.I2c实例的write方法将开启加速度计。
message = bytearray(
[self.__class__.ADXL345_POWER_CTL,
self.__class__.ADXL345_POWER_ON])
if self.i2c.write(message) != mraa.SUCCESS:
raise Exception("i2c.write() control register failed")
我们声明了以下与分辨率相关的常量:
-
ADXL345_FULL_RES:以全分辨率工作,其中分辨率随着 g 范围的增加而增加到 13 位分辨率 -
ADXL345_10BIT:以固定的 10 位分辨率工作
我们声明了以下与量纲相关的常量:
-
ADXL345_2G:将 g 范围设置为±2g -
ADXL345_4G:将 g 范围设置为±4g -
ADXL345_8G:将 g 范围设置为±8g -
ADXL345_16G:将 g 范围设置为±16g
在对传感器配置所需的分辨率和量纲进行另一次写入之前,代码对mraa.I2c实例的address方法进行了另一次调用。代码通过创建一个包含我们想要写入从设备的两个十六进制值的bytearray来构建另一条消息:ADXL345_DATA_FORMAT以及应用位或运算符(|)后的ADXL345_16G和ADXL345_FULL_RES的结果。我们可以将这条消息解读为“将±16g 和全分辨率写入数据格式寄存器”。有必要将所需的分辨率和范围组合成一个单字节值,因此我们必须使用位或运算符(|)。
if self.i2c.address(self.__class__.ADXL345_I2C_ADDR) != mraa.SUCCESS:
raise Exception("i2c.address() failed")
message = bytearray(
[self.__class__.ADXL345_DATA_FORMAT,
self.__class__.ADXL345_16G | self.__class__.ADXL345_FULL_RES])
if self.i2c.write(message) != mraa.SUCCESS:
raise Exception("i2c.write() mode register failed")
调用mraa.I2c实例的write方法并传递此消息将配置加速度计以±16g 的范围工作,并具有完整的分辨率。由于我们有权访问此调用,我们可以修改代码以更改所需的分辨率或加速度测量的比例。例如,以下组成消息的行将配置更改,使加速度计能够以±4g 的范围工作:
message = bytearray(
[self.__class__.ADXL345_DATA_FORMAT,
self.__class__.ADXL345_4G | self.__class__.ADXL345_FULL_RES])
然后,代码声明了 x、y 和 z 的偏移量属性,这是将来自加速度计的原始加速度值转换为以 g 表示的适当值所必需的。我们希望使用易于理解的属性,因此构造函数创建并初始化了三个属性,其值为0.0:x_acceleration、y_acceleration和z_acceleration。最后,构造函数调用update方法以从加速度计检索第一个值。
update方法对mraa.I2c实例的address方法进行调用,然后调用其writeByte方法,将ADXL345_XOUT_L作为其参数,即我们想要读取的第一个数据寄存器。
self.i2c.address(self.__class__.ADXL345_I2C_ADDR)
self.i2c.writeByte(self.__class__.ADXL345_XOUT_L)
加速度计的值存储在六个数据寄存器中。每个轴有两个字节:低字节(八个最低有效位)和高字节(八个最高有效位),因此我们可以通过单个 I²C 读取操作读取六个字节,从 x 轴的第一个字节地址开始。然后,我们必须将每对字节组合成一个单一值。对mraa.I2c实例的read方法的调用将DATA_REG_SIZE常量作为参数传递,以指示我们想要读取六个字节,并且代码将结果bytearray保存到xyz_raw_acceleration局部变量中。
self.i2c.address(self.__class__.ADXL345_I2C_ADDR)
xyz_raw_acceleration = self.i2c.read(self.__class__.DATA_REG_SIZE)
然后,代码将低字节和高字节组合成一个单一值,为从加速度计检索的每个原始加速度字节对保存到三个局部变量中:x_raw_acceleration、y_raw_acceleration和z_raw_acceleration。代码使用二进制左移(<<)位运算符将高字节(八个最高有效位)向左移动 8 位,并将右侧的新位设置为 0。然后,它应用二进制或(|)来构建整个字(两个字节)。x_raw_acceleration值是将高字节和低字节组合起来构成一个双字节字的結果。
xyz_raw_acceleration数组中的第一个元素(xyz_raw_acceleration[0])包括 x 原始加速度的低字节,而xyz_raw_acceleration数组中的第二个元素(xyz_raw_acceleration[1])包括 x 原始加速度的高字节。因此,有必要向高字节(xyz_raw_acceleration[1])添加 8 个二进制零,并用低字节(xyz_raw_acceleration[0])替换这些零。对于 y 和 z 原始加速度字节也要做同样的事情。
x_raw_acceleration = (xyz_raw_acceleration[1] << 8) | xyz_raw_acceleration[0]
y_raw_acceleration = (xyz_raw_acceleration[3] << 8) | xyz_raw_acceleration[2]
z_raw_acceleration = (xyz_raw_acceleration[5] << 8) | xyz_raw_acceleration[4]
最后,我们需要将每个值乘以构造函数中定义的偏移量,以获得以 g 为单位的 x、y 和 z 的适当值,并将它们保存到三个属性中:x_acceleration、y_acceleration 和 z_acceleration。
self.x_acceleration = x_raw_acceleration * self.x_offset
self.y_acceleration = y_raw_acceleration * self.y_offset
self.z_acceleration = z_raw_acceleration * self.z_offset
现在,我们有一个完全用 Python 编写的表示 ADXL345 加速度计的类,我们可以进行任何必要的更改,以对加速度计进行不同的配置。
我们只需要创建 Accelerometer 类的新版本,使用最近创建的 Adxl345 类而不是 pyupm_adxl345.Adxl345 类。以下行显示了新 Accelerometer 类的代码。示例的代码文件为 iot_python_chapter_07_03.py。
class Accelerometer:
def __init__(self, bus):
self.accelerometer = Adxl345(bus)
self.x_acceleration = 0.0
self.y_acceleration = 0.0
self.z_acceleration = 0.0
def measure_acceleration(self):
# Update the acceleration values for the three axis
self.accelerometer.update()
self.x_acceleration = self.accelerometer.x_acceleration
self.y_acceleration = self.accelerometer.y_acceleration
self.z_acceleration = self.accelerometer.z_acceleration
现在,我们可以使用之前示例中用于 __main__ 方法的相同代码,并执行相同的操作来检查从加速度计获取的值。
小贴士
编写与 I²C 总线和特定传感器交互的代码需要很大的努力,因为我们必须从制造商的数据表中读取详细的规格。有时,如果我们不编写自己的代码,可能无法使用传感器中包含的所有功能。在其他情况下,upm 库中包含的功能可能足以满足我们的项目需求。
连接模拟温度传感器
在第六章 使用模拟输入和本地存储 中,我们使用了一个包含在分压器中的光敏电阻,并将其连接到模拟输入引脚。我们可以使用类似的配置,并用热敏电阻替换光敏电阻来测量环境温度。热敏电阻会随着温度变化而改变其电阻值,因此,我们可以将电阻变化转换为电压值变化。
我们还可以使用一个包含热敏电阻的模拟传感器扩展板,该热敏电阻配置为为我们提供一个模拟引脚的电压级别,这些电压级别代表温度值。在这种情况下,我们将使用 upm 库支持的模拟温度传感器来测量环境温度。
我们将使用标有 A0 的模拟引脚来连接模拟加速度计扩展板的电压输出。完成必要的接线后,我们将编写 Python 代码来测量并显示环境温度,单位为摄氏度 (ºC) 和华氏度 (ºF)。这样,我们将读取将模拟值转换为其数字表示的结果,并将其映射到适当的测量单位中的温度值。
我们需要一个 Seeedstudio Grove 温度传感器来配合这个示例。以下网址提供了关于此模块的详细信息:www.seeedstudio.com/depot/Grove-Temperature-Sensor-p-774.html。以下图显示了传感器扩展板、必要的布线以及从 Intel Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件为 iot_fritzing_chapter_07_04.fzz,以下图片是面包板视图。不要忘记,您也可以选择使用 Grove 基础板将此传感器连接到 Intel Galileo Gen 2 板。

以下图片显示了用符号表示的电子元件的电路图:

如前图所示,我们有以下连接:
-
标记为A0的模拟输入引脚连接到标记为SIG的温度输出引脚(在扩展板符号中为0)
-
标记为3V3的电源引脚连接到温度传感器电源引脚标记为VCC
-
标记为GND的接地引脚连接到温度传感器接地引脚标记为GND
现在,是时候进行所有必要的布线了。在添加或移除任何线缆之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 灯熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
使用模拟传感器测量环境温度
upm 库在 pyupm_grove 模块中包括对 Grove 模拟温度传感器扩展板的支持。在此模块中声明的 GroveTemp 类代表连接到我们板上的模拟温度传感器。该类使得从模拟输入读取的原始值转换为以摄氏度(ºC)表示的值变得简单。
我们将创建一个新的 TemperatureSensor 类来表示温度传感器,并使我们更容易检索环境温度值,而无需担心与 GroveTemp 类实例一起工作时必要的单位转换。我们将使用 GroveTemp 类与模拟温度传感器交互。以下行显示了与 upm 库一起工作的新 TemperatureSensor 类的代码,特别是与 pyupm_grove 模块一起。示例的代码文件为 iot_python_chapter_07_04.py。
import pyupm_grove as upmGrove
import time
class TemperatureSensor:
def __init__(self, analog_pin):
self.temperature_sensor = upmGrove.GroveTemp(analog_pin)
self.temperature_celsius = 0.0
self.temperature_fahrenheit = 0.0
def measure_temperature(self):
# Retrieve the temperature expressed in Celsius degrees
temperature_celsius = self.temperature_sensor.value()
self.temperature_celsius = temperature_celsius
self.temperature_fahrenheit = \
(temperature_celsius * 9.0 / 5.0) + 32.0
当我们创建 TemperatureSensor 类的实例并在 analog_pin 必需参数中指定传感器连接的模拟引脚时,我们必须指定该引脚。构造函数,即 __init__ 方法,创建一个新的 upmGrove.GroveTemp 实例,并使用接收到的 analog_pin 参数,将其引用保存在 temperature_sensor 属性中。最后,构造函数实例创建并初始化两个属性,值为 0.0:temperature_celsius 和 temperature_fahrenheit。
该类定义了一个 measure_temperature 方法,通过调用 self.temperature_sensor 的值方法来获取当前环境温度(单位为摄氏度,ºC),并将该值保存在局部变量 temperature_celsius 中。下一行将值赋给 temperature_celsius 属性。最后,代码将摄氏度(ºC)测量的温度转换为等效的华氏度(ºF)值。该公式易于阅读,因为它只需要将摄氏度(ºC)测量的温度乘以 9,然后将结果除以 5 并加上 32。这样,TemperatureSensor 类就更新了两个属性,即传感器测量的环境温度(单位为摄氏度,ºC)和华氏度(ºF)。
现在,我们将编写一个循环,每 10 秒检索并显示环境温度,单位为摄氏度(ºC)和华氏度(ºF)。该示例的代码文件为 iot_python_chapter_07_04.py。
if __name__ == "__main__":
# The temperature sensor is connected to analog pin A0
temperature_sensor = TemperatureSensor(0)
while True:
temperature_sensor.measure_temperature()
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_sensor.temperature_fahrenheit))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
第一行创建了一个之前编写的 TemperatureSensor 类的实例,analog_pin 参数的值为 0。这样,该实例将从标有 A0 的引脚读取模拟值。然后,代码运行一个无限循环,调用 measure_temperature 方法来更新环境温度值,然后打印它们,单位为摄氏度(ºC)和华氏度(ºF)。
以下行将启动示例:
python iot_python_chapter_07_04.py
运行示例后,打开空调或加热系统以产生环境温度的变化,你将看到测量温度在几分钟后的变化。以下行显示了部分示例输出:
Ambient temperature in degrees Celsius: 13
Ambient temperature in degrees Fahrenheit: 55.4
Ambient temperature in degrees Celsius: 14
Ambient temperature in degrees Fahrenheit: 57.2
Ambient temperature in degrees Celsius: 15
Ambient temperature in degrees Fahrenheit: 59
Ambient temperature in degrees Celsius: 16
Ambient temperature in degrees Fahrenheit: 60.8
将数字温度和湿度传感器连接到 I²C 总线
现在,我们将使用一个多功能数字传感器,它将为我们提供温度和相对湿度信息。我们将使用一个使用 I²C 总线进行通信的电路板,以便英特尔 Galileo Gen 2 板与传感器通信。当不需要在极端条件下测量温度和湿度时,该传感器很有用。我们不能在厄特纳火山顶部使用此传感器,以防我们在与火山相关的科研项目中工作。
我们将使用标有SDA和SCL的两个引脚将 I²C 总线的数据线和时钟线连接到数字温度和湿度引脚扩展板上的相应引脚。完成必要的布线后,我们将编写 Python 代码来测量、显示环境温度和相对湿度。这样,我们将通过 I²C 总线发送命令到传感器,读取响应,并将它们解码为以适当单位表示的环境温度和相对湿度。
我们需要一款 SeeedStudio Grove 温度与湿度传感器(高精度且迷你)的引脚扩展板来与这个示例一起使用。以下网址提供了关于这款引脚扩展板的详细信息:www.seeedstudio.com/depot/Grove-TemperatureHumidity-Sensor-HighAccuracy-Mini-p-1921.html。该引脚扩展板集成了 TH02 数字湿度温度传感器,并支持 I²C 总线。
以下图表显示了数字温度、湿度引脚扩展板、必要的布线以及从 Intel Galileo Gen 2 板到面包板的布线。该示例的 Fritzing 文件为iot_fritzing_chapter_07_05.fzz,以下图片是面包板视图:

以下图片显示了带有电子元件符号的电路图:

如前图所示,我们有以下连接:
-
SDA引脚连接到标有SDA的引脚扩展板。这样,我们将数字温度和湿度传感器连接到 I²C 总线的串行数据线。
-
SCL引脚连接到标有SCL的引脚扩展板。这样,我们将数字温度和湿度传感器连接到 I²C 总线的串行时钟线。
-
标有3V3的电源引脚连接到标有VCC的引脚扩展板电源引脚。
-
标有GND的接地引脚连接到标有GND的引脚扩展板接地引脚。
现在,是时候进行所有必要的布线了。在添加或移除任何线从板上的引脚之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
使用数字传感器测量温度和湿度
upm 库在 pyupm_th02 模块中包含了支持使用 TH02 传感器的数字温度和湿度扩展板。在此模块中声明的 TH02 类代表一个使用 TH02 传感器的数字温度和湿度传感器,该传感器连接到我们的板上。该类使得初始化传感器并通过 I²C 总线检索温度和湿度值变得简单。该类在幕后与 mraa.I2c 类协同工作,与传感器通信,即向 TH02 传感器写入数据并从该传感器读取数据,该传感器作为连接到 I²C 总线的从设备。
我们将创建一个新的 TemperatureAndHumiditySensor 类来表示温度和湿度传感器,并使我们在使用 TH02 类的实例时更容易检索温度和湿度值。我们将使用 TH02 类与传感器交互。以下行显示了与 upm 库(特别是 pyupm_th02 模块)一起工作的新 TemperatureSensor 类的代码。示例的代码文件为 iot_python_chapter_07_05.py。
import pyupm_th02 as upmTh02
import time
class TemperatureAndHumiditySensor:
def __init__(self, bus):
self.th02_sensor = upmTh02.TH02(bus)
self.temperature_celsius = 0.0
self.temperature_fahrenheit = 0.0
self.humidity = 0.0
def measure_temperature_and_humidity(self):
# Retrieve the temperature expressed in Celsius degrees
temperature_celsius = self.th02_sensor.getTemperature()
self.temperature_celsius = temperature_celsius
self.temperature_fahrenheit = \
(temperature_celsius * 9.0 / 5.0) + 32.0
# Retrieve the humidity
self.humidity = self.th02_sensor.getHumidity()
当我们在 TemperatureAndHumiditySensor 类的 bus 必需参数中创建实例时,我们必须指定数字温度和湿度传感器连接到的 I²C 总线编号。构造函数,即 __init__ 方法,使用接收到的 bus 参数创建一个新的 upmTh02.TH02 实例,并将它的引用保存在 th02_sensor 属性中。
小贴士
TH02 传感器的数据表指定了一个将原始读取温度转换为摄氏度(ºC)的公式,因此,通过阅读数据表,我们可能会认为 upmTh02.TH02 实例将提供一个华氏度(ºF)的值。然而,事实并非如此。upmTh02.TH02 实例将华氏度(ºF)转换为摄氏度(ºC),并为我们提供一个后者的测量单位值。因此,如果我们想以华氏度(ºF)显示值,我们必须将摄氏度(ºC)转换为华氏度(ºF)。不幸的是,了解这种情况的唯一方法是通过查看 upm 模块的 C++ 源代码,因为关于代码使用的测量单位没有文档说明。
我们希望使用易于理解的属性,因此构造函数创建了三个属性并初始化为 0.0:temperature_celsius、temperature_fahrenheit 和 humidity。构造函数执行后,我们有一个初始化的数字温度和湿度传感器,可以检索值。
该类定义了一个 measure_temperature_and_humidity 方法,该方法更新传感器中的环境温度和湿度值,检索这些值,并将它们最终保存到以下三个属性中:temperature_celsius、temperature_fahrenheit 和 humidity。
首先,measure_temperature_and_humidity方法中的代码调用self.th02_sensor的getTemperature方法,请求传感器检索温度值。该方法返回转换为摄氏度(ºC)的读取值,代码将其保存到temperature_celsius局部变量中。代码将值保存在具有相同名称的属性中,并将转换为华氏度(ºF)的值保存在temperature_fahrenheit属性中。最后,代码调用self.th02_sensor的getHumidity方法,请求传感器检索湿度值并将其保存在humidity属性中。
现在,我们将编写一个循环,每 10 秒检索并显示以摄氏度(ºC)和华氏度表示的温度值,以及湿度值。示例的代码文件是iot_python_chapter_07_05.py。
if __name__ == "__main__":
temperature_and_humidity_sensor = \
TemperatureAndHumiditySensor(0)
while True:
temperature_and_humidity_sensor.\
measure_temperature_and_humidity()
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor.temperature_fahrenheit))
print("Ambient humidity: {0}%".
format(temperature_and_humidity_sensor.humidity))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
第一行创建了一个之前编写的TemperatureAndHumiditySensor类的实例,bus参数的值为0。这样,该实例将通过 I²C 总线与数字加速度计建立通信。正如我们在之前与 I²C 总线连接的传感器示例中发生的那样,英特尔 Galileo Gen 2 板是总线上的主设备,而数字温度和湿度传感器则作为从设备。
然后,代码运行一个无限循环,调用measure_temperature_and_humidity方法来更新以两种单位和湿度表示的温度值。
以下行将开始示例:
python iot_python_chapter_07_05.py
在运行示例后,打开空调或供暖系统,以产生环境温度和湿度的变化。
Ambient temperature in degrees Celsius: 24
Ambient temperature in degrees Fahrenheit: 73.4
Ambient humidity: 48%
测试你的知识
-
以下哪个传感器允许我们测量正加速度的大小和方向?
-
一个温度传感器。
-
一个加速度计。
-
一个光传感器。
-
-
以下哪个缩写定义了一个具有传感器的模块的连接类型是模拟的:
-
AIO。
-
I2C。
-
UART。
-
-
我们需要多少根线来将设备连接到 I²C 总线:
-
1`。
-
2`。
-
3`。
-
-
我们需要多少根线来将设备连接到 SPI 总线:
-
1`。
-
2`。
-
3`。
-
-
以下哪个不是 I²C 总线的连接:
-
MISO。
-
SDA。
-
SCL。
-
摘要
在本章中,我们学习了传感器及其连接类型。我们了解到在选择传感器时需要考虑许多重要事项,并且它们使我们能够轻松地从现实世界测量不同的变量。我们学习了考虑测量单位的重要性,因为传感器总是以特定的单位提供测量值,我们必须考虑这些单位。
我们编写了利用upm库中包含的模块和类来简化我们开始使用模拟和数字传感器的代码。此外,我们还编写了通过 I²C 总线与数字加速度计交互的代码,因为我们想利用传感器提供的额外功能,但这些功能并未包含在upm库模块中。
我们测量了合加速度或 g 力的幅度和方向、环境温度和湿度。与前面的章节一样,我们继续利用 Python 的面向对象特性,并创建了类来封装upm和mraa库中的传感器和必要的配置。我们的代码易于阅读和理解,并且我们可以轻松地隐藏底层细节。
现在我们能够通过传感器从现实世界获取数据,我们将使我们的物联网设备通过不同的执行器和屏蔽器执行动作,这是下一章的主题。
第八章. 显示信息并执行操作
在本章中,我们将使用各种扩展板和执行器,通过编写 Python 代码来显示数据和执行操作。我们将:
-
理解 LCD 显示屏及其连接类型
-
学习在选择 LCD 显示屏时必须考虑的最重要事项
-
利用 LCD 显示屏和执行器利用
upm库 -
使用与 I²C 总线兼容的 RGB 背光 LCD 显示屏
-
在 16x2 LCD 屏幕上显示和更新文本
-
使用与 I²C 总线兼容的 OLED 显示屏
-
在 96x96 点阵 OLED 显示屏上显示和更新文本
-
将标准伺服电机连接到 PWM 进行控制
-
使用伺服电机和轴显示值
理解 LCD 显示屏及其连接类型
有时,我们的物联网设备必须通过连接到英特尔 Galileo Gen 2 板的任何设备向用户提供信息。我们可以使用不同类型的电子组件、屏蔽或扩展板来实现这一目标。
例如,我们可以使用简单的 LED 灯来提供可以用颜色表示的信息。例如,一个亮起的红色 LED 可以表示连接到板上的温度传感器检测到环境温度高于 80 华氏度(ºF)或 26.66 摄氏度(ºC)。一个亮起的蓝色 LED 可以表示我们的温度传感器检测到环境温度低于 40 华氏度(ºF)或 4.44 摄氏度(ºC)。一个亮起的红色 LED 可以表示温度介于这两个值之间。这三个 LED 灯使我们能够向用户提供有价值的信息。
我们也可以使用单个 RGB LED 并利用脉冲宽度调制(PWM)来根据测量的环境温度值改变其颜色,正如我们在第四章中学习的,使用 RESTful API 和脉冲宽度调制。
然而,有时颜色不足以向用户提供详细和准确的信息。例如,有时我们希望用百分比值显示湿度水平,而几个 LED 灯不足以表示从 0 到 100%的数字。如果我们想要能够显示 1%的步进值,我们就需要 100 个 LED 灯。我们没有 100 个 GPIO 引脚,因此我们需要一个带有 100 个 LED 灯和数字接口(如 I²C 总线)的屏蔽或扩展板,以便我们可以发送指示要打开的 LED 灯数量的命令。
在这些情况下,一个允许我们打印特定数量字符的液晶屏幕可能是一个合适的解决方案。例如,在一个允许我们每行显示 16 个字符的液晶屏幕上,有 2 行 16 个字符,称为 16x2 液晶模块,我们可以在第一行显示温度,在第二行显示湿度水平。以下表格显示了每一行文本和值的示例,考虑到我们有 16 列和 2 行字符。
| T | e | m | p | . | 4 | 0 | . | 2 | F | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| H | u | m | i | d | i | t | y | 8 | 0 | % |
16x2 液晶模块为每个值提供清晰的描述,包括浮点值和度量单位。因此,我们将使用 16x2 液晶模块作为示例。以下图片显示了 16x2 液晶屏幕中每个字符位置的示例:

有不同功能的液晶模块,我们必须考虑我们在第七章“使用传感器从现实世界获取数据”中分析传感器时学到的大量内容。以下列表列举了在选择液晶模块时我们必须考虑的最重要的因素及其描述。由于我们在学习传感器时分析了这些内容中的许多,因此我们不会重复常见项目的描述。
-
与英特尔 Galileo Gen 2 板和我们所使用的电压供应(5V 或 3.3V)的兼容性。
-
功耗。
-
连接类型:一些液晶显示器消耗太多的引脚,因此,检查它们所需的全部引脚非常重要。液晶显示器最常见的连接类型是 I²C 总线、SPI 总线和 UART 端口。然而,一些液晶显示器需要总线或端口与额外的 GPIO 引脚结合使用。
-
工作范围和特殊环境要求。
-
尺寸:液晶显示器有不同的尺寸。有时只有特定的尺寸适合我们的项目。
-
列数和行数:根据我们必须显示的文本,我们将选择具有适当列数和行数的液晶显示器,以便显示字符。
-
响应时间:确定我们能够等待液晶显示器显示替换正在显示的文本或清除显示器的新内容的时间非常重要。
-
协议,upm 库中的支持以及 Python 绑定。
-
支持的字符集和内置字体:一些液晶显示器支持用户自定义字符,因此,它们允许我们配置和显示自定义字符。检查液晶显示器是否支持我们必须显示的文本的语言中的字符也很重要。
-
背光颜色、文本颜色和对比度级别:一些 LCD 显示器允许我们更改背光颜色,而另一些则具有固定的背光颜色。RGB 背光使我们能够结合红色、绿色和蓝色组件来确定所需的背光颜色。此外,始终需要考虑对比度级别是否适合您需要显示信息的照明条件。
-
成本。
将 LCD RGB 背光连接到 I²C 总线
在我们的第七章“使用传感器从现实世界获取数据”的最后一个示例中,我们使用了一个多功能数字传感器,为我们提供了温度和相对湿度信息。我们使用了一个使用 I²C 总线允许 Intel Galileo Gen 2 板与传感器通信的扩展板。现在,我们将添加一个带有 16x2 LCD RGB 背光的扩展板,以便我们可以用文本和数字显示测量的温度和湿度值。
LCD RGB 背光扩展板也将连接到与温度和湿度数字传感器相同的 I²C 总线。只要它们的 I²C 地址不同,我们就可以在 Intel Galileo Gen 2 板上将许多从设备连接到 I²C 总线。实际上,LCD RGB 背光扩展板有两个 I²C 地址:一个用于 LCD 显示器,另一个用于背光。
我们需要以下部分来使用这个示例:
-
一块 SeeedStudio Grove 温度和湿度传感器(高精度和迷你)扩展板。以下网址提供了有关此扩展板的详细信息:
www.seeedstudio.com/depot/Grove-TemperatureHumidity-Sensor-HighAccuracy-Mini-p-1921.html。 -
一块 SeeedStudio Grove LCD RGB 背光扩展板。以下网址提供了有关此扩展板的详细信息:
www.seeedstudio.com/depot/Grove-LCD-RGB-Backlight-p-1643.html。
以下图表显示了数字温度和湿度扩展板、LCD RGB 背光扩展板、必要的连接以及从 Intel Galileo Gen 2 板到面包板的连接。该示例的 Fritzing 文件为iot_fritzing_chapter_08_01.fzz,以下图像是面包板视图:

以下图像显示了用符号表示的电子元件的原理图:

如前一个原理图所示,我们有以下连接:
-
SDA引脚连接到标记为SDA的扩展板引脚。这样,我们将数字温度和湿度传感器以及 LCD 背光连接到 I²C 总线的串行数据线。
-
SCL 引脚连接到标有 SCL 的扩展板引脚。这样,我们可以将数字温度和湿度传感器以及 LCD 背光连接到 I²C 总线的串行时钟线上。
-
标有 3V3 的电源引脚连接到标有 VCC 的数字温度和湿度传感器扩展板电源引脚。
-
标有 5V 的电源引脚连接到标有 VCC 的 LCD 背光扩展板电源引脚。
-
标有 GND 的地线连接到标有 GND 的扩展板引脚。
现在,是时候进行所有必要的接线了。在从板上的引脚添加或移除任何电线之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 灯熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
在 LCD 显示器上显示文本
upm 库在 pyupm_i2clcd 模块中包括对 16x2 LCD RGB 背光扩展板的支持。在此模块中声明的 Jhd1313m1 类代表一个 16x2 LCD 显示屏及其 RGB 背光,连接到我们的板上。该类使得设置 RGB 背光的颜色组件、清除 LCD 显示、指定光标位置以及通过 I²C 总线写入文本变得简单。该类在幕后与 mraa.I2c 类一起工作,以与 RGB 背光和 LCD 显示进行通信。这两个设备作为连接到 I²C 总线的从设备,因此,它们在这个总线中都有特定的地址。
我们将使用上一章中编写的代码来读取传感器的温度和湿度值,并将此代码作为基准来添加新功能。示例代码文件为 iot_python_chapter_07_05.py。
我们将创建一个 Lcd 类来表示 16x2 LCD RGB 背光,并使我们在设置背景颜色和在不担心 Jhd1313m1 类实例的具体方法的情况下写入两行文本时更加容易。我们将使用 Jhd1313m1 类与 LCD 及其 RGB 背光进行交互。以下行显示了与 upm 库一起工作的新 Lcd 类的代码,特别是与 pyupm_i2clcd 模块一起工作。示例代码文件为 iot_python_chapter_08_01.py。
import pyupm_th02 as upmTh02
import pyupm_i2clcd as upmLcd
import time
class Lcd:
# The I2C address for the LCD display
lcd_i2c_address = 0x3E
# The I2C address for the RBG backlight
rgb_i2c_address = 0x62
def __init__(self, bus, red, green, blue):
self.lcd = upmLcd.Jhd1313m1(
bus,
self.__class__.lcd_i2c_address,
self.__class__.rgb_i2c_address)
self.lcd.clear()
self.set_background_color(red, green, blue)
def set_background_color(self, red, green, blue):
self.lcd.setColor(red, green, blue)
def print_line_1(self, message):
self.lcd.setCursor(0, 0)
self.lcd.write(message)
def print_line_2(self, message):
self.lcd.setCursor(1, 0)
self.lcd.write(message)
Lcd 类声明了两个类属性:lcd_i2c_address 和 rgb_i2c_address。第一个类属性定义了 LCD 显示的 I²C 地址,即当光标位于特定行和列时,将处理定位光标和写入文本的命令的地址。该地址为十六进制的 3E(即 0x3E)。如果我们只是在代码中看到 0x3E,我们不会理解它是一个 LCD 显示的 I²C 总线地址。第二个类属性定义了 RGB 背光的 I²C 地址,即处理设置背光红色、绿色和蓝色组件的命令的地址。该地址为十六进制的 62(即 0x62)。如果我们只是在代码中看到 0x62,我们不会理解它是一个 RGB 背光的 I²C 总线地址。这些类属性使得代码更容易阅读。
当我们在 bus 必要参数中创建 Lcd 类的实例时,我们必须指定连接到 16x2 LCD 和 RGB 背光的 I²C 总线编号。此外,还需要指定红色、绿色和蓝色颜色组件的值来配置 RGB 背光的背景颜色。构造函数,即 __init__ 方法,使用接收到的 bus 参数以及 lcd_i2c_address 和 rgb_i2c_address 类属性创建一个新的 upmLcd.Jhd1313m1 实例,并将新实例的引用保存在 lcd 属性中。然后,代码调用新实例的 clear 方法来清除 LCD 屏幕。最后,代码调用 set_background_color 方法,并使用作为参数接收的红色、绿色和蓝色值来配置 RGB 背光的背景颜色。
该类声明了 set_background_color 方法,该方法使用接收到的 red、green 和 blue 值调用 lcd.setColor 方法。在底层,upmLcd.Jhd1313m1 实例将通过 I²C 总线将数据写入地址等于 rgb_i2c_address 类属性的从设备,以指定每个颜色组件的期望值。我们只是创建了一个特定方法来遵循 Python 命名约定,并使使用我们的类的最终代码更容易阅读。
该类定义了以下两个额外的方法,以便于在 LCD 显示的第一行和第二行打印文本:
-
print_line_1 -
print_line_2
print_line_1方法调用upmLcd.Jhd1313m1实例(self.lcd)的setCursor方法,其中row和column参数的值均为0,以将光标定位在第一行和第一列。然后,调用upmLcd.Jhd1313m1实例(self.lcd)的写入方法,将message作为参数传递,在 LCD 显示屏中打印接收到的字符串。在底层,upmLcd.Jhd1313m1实例将通过 I²C 总线将数据写入地址等于lcd_i2c_address类属性的从设备,以指定光标所需的位置,然后从我们定位光标的位置开始写入指定的文本。第一行用 0 标识,但我们将其命名为print_line_1,因为这使我们更容易理解我们正在在 LCD 屏幕的第一行写入消息。
print_line_2方法与print_line_1方法具有相同的两行代码,只是对setCursor方法的调用指定了1作为行参数的值。这样,该方法将在 LCD 屏幕的第二行打印消息。
现在,我们将创建一个名为TemperatureAndHumidityLcd的之前编写的Lcd类的子类。该子类将专门化Lcd类,使我们能够轻松地在 LCD 屏幕的第一行打印华氏度表示的温度值,并在第二行打印百分比表示的湿度值。以下行显示了新TemperatureAndHumidityLcd类的代码。示例的代码文件是iot_python_chapter_08_01.py。
class TemperatureAndHumidityLcd(Lcd):
def print_temperature(self, temperature_fahrenheit):
self.print_line_1("Temp. {:5.2f}F".format(temperature_fahrenheit))
def print_humidity(self, humidity):
self.print_line_2("Humidity {0}%".format(humidity))
新的类(TemperatureAndHumidityLcd)向其超类(Lcd)添加了以下两个方法:
-
print_temperature:调用print_line_1方法,并使用在temperature_fahrenheit参数中接收到的格式化文本来显示温度值。 -
print_humidity:调用print_line_2方法,并使用在humidity参数中接收到的格式化文本来显示湿度水平。
现在,我们将编写一个循环,每 10 秒在 LCD 屏幕上显示环境温度(华氏度,ºF)和湿度值。示例的代码文件是iot_python_chapter_08_01.py。
if __name__ == "__main__":
temperature_and_humidity_sensor = \
TemperatureAndHumiditySensor(0)
lcd = TemperatureAndHumidityLcd(0, 0, 0, 128)
while True:
temperature_and_humidity_sensor.\
measure_temperature_and_humidity()
lcd.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit)
lcd.print_humidity(
temperature_and_humidity_sensor.humidity)
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor.temperature_fahrenheit))
print("Ambient humidity: {0}".
format(temperature_and_humidity_sensor.humidity))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
突出的行显示了与上一个版本相比对__main__方法所做的更改。第一行突出显示了使用0作为bus参数的值、0用于red和green以及128用于blue来设置背景颜色为浅蓝色的新实例TemperatureAndHumidityLcd类的代码。该代码将此实例的引用保存到lcd局部变量中。这样,该实例将通过 I²C 总线与 LCD 屏幕和 RGB 背光建立通信。RGB 背光将显示浅蓝色背景。
然后,代码运行一个无限循环,并使用突出显示的行调用lcd.print_temperature方法,并使用temperature_and_humidity_sensor.temperature_fahrenheit作为参数,即以华氏度(ºF)表示的测量温度。这样,代码在 LCD 屏幕的第一行显示这个温度值。
下一条突出显示的行调用lcd.print_humidity方法,并使用temperature_and_humidity_sensor.humidity作为参数,即以百分比表示的测量湿度。这样,代码在 LCD 屏幕的第二行显示这个湿度值。
以下行将启动示例:
python iot_python_chapter_08_01.py
在运行示例之后,打开空调或加热系统,以产生环境温度和湿度的变化。LCD 屏幕将显示温度和湿度,并且每 10 秒刷新一次。
将 OLED 点阵连接到 I²C 总线
当我们需要通过 I²C 或 SPI 总线在外部屏幕上显示内容时,LCD 显示屏并不是唯一的选择。还有一些 OLED 点阵,允许我们控制特定数量的点。在 OLED 点阵中,我们可以控制每个点,而不是控制每个字符空间。其中一些是灰度的,而另一些是 RGB 的。
OLED 点阵的关键优势在于我们可以显示任何类型的图形,而不仅仅是文本。实际上,我们可以将任何类型的图形和图像与文本混合。Grove OLED Display 0.96"是一个 16 灰度 96-by-96 点阵 OLED 显示屏模块的例子,它支持 I²C 总线。以下 URL 提供了关于这块扩展板的详细信息:www.seeedstudio.com/depot/Grove-OLED-Display-096-p-824.html。Xadow RGB OLED 96x24 是一个 RGB 彩色 96-by-64 点阵 OLED 显示屏模块的例子,它支持 SPI 总线。以下 URL 提供了关于这块扩展板的详细信息:www.seeedstudio.com/depot/Xadow-RGB-OLED-96x64-p-2125.html。
小贴士
另一个选择是与 TFT LCD 点阵或显示屏一起工作。其中一些包括触摸检测的支持。
现在,我们将用一块带有 16 灰度 96-by-96 点阵的 OLED 显示屏的 16x2 LCD RGB 背光板来替换扩展板,这块显示屏也支持 I²C 总线,我们将使用这块新屏幕以不同的配置显示类似值。这些接线与之前的扩展板兼容。
如同我们之前的示例,点阵 OLED 也将连接到与温度和湿度数字传感器相同的 I²C 总线。由于点阵 OLED 的 I²C 地址与温度和湿度数字传感器使用的地址不同,因此我们不需要将两个设备连接到同一个 I²C 总线上。
我们需要以下额外的部件来使用此示例:一个 SeeedStudio Grove OLED 显示屏 0.96 英寸,16 灰度 96x96 点阵 OLED 显示模块。96x96 点阵 OLED 显示屏为我们提供了控制 9,216 个点,即像素的机会。然而,在这种情况下,我们只想使用 OLED 显示屏来显示类似于我们之前示例中的文本,但布局不同。
如果我们使用默认的 8x8 字符框,我们就有 12 列(96/8)和 12 行(96/8)用于字符。以下表格显示了每行文本及其值的示例。
| 温 | 度 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| 法 | 雷 | 尼 | 哈恩 | 海 | 特 | 海 | 伊 | 特 | |||
| 4 | 0 | . | 2 | ||||||||
| 细 | 节 | ||||||||||
| 4 | . | 5 | 5 | ||||||||
| 湿 | 度 | ||||||||||
| 级 | 别 | ||||||||||
| 8 | 0 | % | |||||||||
能够使用 12 列和 12 行的字符,使我们能够为每个值提供非常清晰的描述。此外,我们能够显示以华氏度和摄氏度表示的温度值。以下图片显示了带有 8x8 字符框的 96x96 点阵 OLED 显示模块中每个字符的位置示例。

在我们将 LCD 屏幕扩展板替换为 OLED 模块后,我们将有以下连接:
-
SDA引脚连接到标记为SDA的扩展板引脚。这样,我们将数字温度和湿度传感器以及 OLED 模块连接到 I²C 总线的串行数据线。
-
SCL引脚连接到标记为SCL的扩展板引脚。这样,我们将数字温度和湿度传感器以及 OLED 模块连接到 I²C 总线的串行时钟线。
-
标记为3V3的电源引脚连接到数字温度和湿度传感器扩展板的标记为VCC的电源引脚。
-
标记为5V的电源引脚连接到 OLED 模块的标记为VCC的电源引脚。
-
标记为GND的地线连接到标记为GND的扩展板引脚。
现在,是时候进行所有必要的接线了。在添加或从板上的引脚移除任何电线之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
在 OLED 显示屏上显示文本
upm库在pyupm_i2clcd模块中包括了对 SeeedStudio Grove OLED 显示屏 0.96 英寸、16 灰度 96x96 点阵 OLED 显示屏模块的支持。由于该 OLED 显示屏使用 SSD1327 驱动集成电路,因此在此模块中声明的SSD1327类代表一个 96x96 点阵 OLED 显示屏,连接到我们的板上。该类使得清除 OLED 屏幕、绘制位图图像、指定光标位置和通过 I²C 总线写入文本变得容易。该类在幕后与mraa.I2c类一起工作,以与 OLED 显示屏通信。
我们将创建一个新的Oled类,该类将代表 96x96 点阵 OLED 显示屏,并使用其默认的 8x8 字符框来显示文本。我们将使用SSD1327类与 OLED 显示屏进行交互。以下行显示了与upm库一起工作的新Oled类的代码,特别是与pyupm_i2clcd模块及其SSD1327类。示例代码文件为iot_python_chapter_08_02.py:
class Oled:
# The I2C address for the OLED display
oled_i2c_address = 0x3C
def __init__(self, bus, red, green, blue):
self.oled = upmLcd.SSD1327(
bus,
self.__class__.oled_i2c_address)
self.oled.clear()
def print_line(self, row, message):
self.oled.setCursor(row, 0)
self.oled.setGrayLevel(12)
self.oled.write(message)
Oled类声明了oled_i2c_address类属性,该属性定义了 OLED 显示屏的 I²C 地址,即一旦光标定位在特定的行和列,将处理定位光标和写入文本的命令的地址。该地址为十六进制的3C(0x3C)。
在创建Oled类的实例时,我们必须指定 OLED 显示屏所连接的 I²C 总线编号,该参数在bus必需参数中。构造函数,即__init__方法,使用接收到的bus参数和oled_i2c_address类属性创建一个新的upmLcd. SSD1327实例,并将新实例的引用保存在oled属性中。最后,代码调用新实例的clear方法以清除 OLED 屏幕。
该类声明了print_line方法,以便于在特定行上打印文本。代码调用upmLcd.SSD1327实例(self.oled)的setCursor方法,将接收到的row值作为row参数的值,并将0作为column参数的值,以将光标定位在指定的行和第一列。然后,调用setGrayLevel和write方法,将upmLcd.SSD1327实例(self.oled)的message接收作为参数的参数,以默认的 8x8 字符框和灰度设置为 12 打印接收到的字符串到 OLED 显示屏。在幕后,upmLcd.SSD1327实例将通过 I²C 总线将数据写入地址等于oled_i2c_address类属性的从设备,以指定光标所需的位置,然后从我们定位光标的位置开始写入指定的文本。
现在,我们将创建一个名为TemperatureAndHumidityOled的子类,该子类基于之前编写的Oled类。这个子类将专门化Oled类,使我们能够轻松地打印华氏度表示的温度值、摄氏度表示的温度值以及百分比表示的湿度值。我们将使用之前解释过的文本布局。以下行显示了新TemperatureAndHumidityOled类的代码。示例的代码文件是iot_python_chapter_08_02.py。
class TemperatureAndHumidityOled(Oled):
def print_temperature(self, temperature_fahrenheit, temperature_celsius):self.oled.clear()
self.print_line(0, "Temperature")
self.print_line(2, "Fahrenheit")
self.print_line(3, "{:5.2f}".format(temperature_fahrenheit))
self.print_line(5, "Celsius")
self.print_line(6, "{:5.2f}".format(temperature_celsius))
def print_humidity(self, humidity):
self.print_line(8, "Humidity")
self.print_line(9, "Level")
self.print_line(10, "{0}%".format(humidity))
新的类(TemperatureAndHumidityOled)向其超类(Oled)添加了以下两个方法:
-
print_temperature: 多次调用print_line方法来显示接收到的温度参数,以华氏度(ºF)和摄氏度(ºC)的形式 -
print_humidity: 多次调用print_line方法来显示接收到的湿度参数
小贴士
在这种情况下,我们刷新许多行来更改仅几个值。由于我们将每 10 秒运行一个循环,所以这不会成为问题。然而,在其他情况下,如果我们想在更短的时间内更新值,我们可以编写优化后的代码,该代码仅清除单行并更新该行中的特定值。
现在,我们将编写一个循环,每 10 秒在 OLED 屏幕上显示以华氏度(ºF)和摄氏度(ºC)表示的环境温度以及以百分比表示的湿度值。示例的代码文件是iot_python_chapter_08_02.py。
if __name__ == "__main__":
temperature_and_humidity_sensor = \
TemperatureAndHumiditySensor(0)
oled = TemperatureAndHumidityOled(0)
while True:
temperature_and_humidity_sensor.\
measure_temperature_and_humidity()
oled.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit,
temperature_and_humidity_sensor.temperature_celsius)
oled.print_humidity(
temperature_and_humidity_sensor.humidity)
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor.temperature_fahrenheit))
print("Ambient humidity: {0}".
format(temperature_and_humidity_sensor.humidity))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
高亮行显示了与上一个版本相比在__main__方法中做出的更改。第一条高亮行使用0作为bus参数的值创建了一个之前编写的TemperatureAndHumidityOled类的实例。代码将此实例的引用保存在oled局部变量中。这样,实例将通过 I²C 总线与 OLED 屏幕建立通信。
然后,代码无限循环运行,高亮行调用oled.print_temperature方法,并使用temperature_and_humidity_sensor.temperature_fahrenheit和temperature_and_humidity_sensor.temperature_celsius作为参数。这样,代码在 OLED 屏幕的第一行显示这两个温度值。
下一条高亮行调用了oled.print_humidity方法,并使用temperature_and_humidity_sensor.humidity。这样,代码通过多行来显示这个湿度值在 OLED 屏幕的底部。
以下行将启动示例:
python iot_python_chapter_08_02.py
运行示例后,打开空调或加热系统以产生环境温度和湿度的变化。OLED 屏幕将显示温度和湿度,并且每 10 秒刷新一次。
连接伺服电机
到目前为止,我们一直在使用传感器从现实世界获取数据,并在液晶显示屏和 OLED 显示屏上显示信息。然而,物联网设备并不仅限于感应和显示数据,它们还可以移动物体。我们可以将不同的组件、屏蔽或分线板连接到我们的英特尔 Galileo Gen 2 板上,并编写 Python 代码来移动连接到板上的物体。
标准伺服电机对于精确控制轴并使其在 0 到 180 度之间的各种角度定位非常有用。在第四章中,使用 RESTful API 和脉冲宽度调制,我们使用了脉冲宽度调制,简称 PWM,来控制 LED 和 RGB LED 的亮度。我们还可以使用 PWM 来控制标准模拟伺服电机,并使其轴在特定角度定位。
小贴士
标准伺服电机是包含齿轮和反馈控制回路电路的直流电机,它提供了精确的位置定位。它们非常适合齿轮转向、机器人手臂和腿部,以及其他需要精确定位的应用。标准伺服电机不需要电机驱动器。
显然,不是所有的伺服电机都具有相同的特性,我们在为我们的项目选择特定的伺服电机时必须考虑许多因素。这取决于我们需要定位什么,精度,所需的扭矩,最佳伺服电机旋转速度等因素。在这种情况下,我们将专注于使用 PWM 定位标准伺服电机。然而,你不能用同一个伺服电机旋转比你需要旋转的重型机械臂更轻的塑料件。对于每个任务,有必要研究合适的伺服电机。
现在,我们将把标准高灵敏度微型伺服电机连接到我们的现有项目中,并将旋转轴以华氏度数显示测量的温度。轴将允许我们在半圆形的量角器上显示测量的温度,该量角器以度为单位测量角度,并将显示从 0 到 180 度的角度数值。伺服电机与轴和量角器的组合将允许我们通过移动部件显示温度。然后,我们可以创建自己的量角器,带有可以添加颜色、特定阈值和许多其他视觉效果的刻度,使温度测量更有趣。具体来说,我们可以创建一个仪表盘图表、速度计或半圆形甜甜圈,即一个饼图和甜甜圈的组合,在单个图表中显示不同的温度值。以下图片显示了我们可以与伺服电机和轴一起使用的半圆形量角器示例。

我们需要以下附加部件来使用此示例:SeeedStudio Grove 伺服或 EMAX 9g ES08A 高灵敏度迷你伺服。以下网址提供了关于这些伺服的详细信息:www.seeedstudio.com/depot/Grove-Servo-p-1241.html 和 www.seeedstudio.com/depot/EMAX-9g-ES08A-High-Sensitive-Mini-Servo-p-760.html。
下图显示了数字温度和湿度扩展板、LCD RGB 背光扩展板、迷你伺服、必要的布线以及从 Intel Galileo Gen 2 板到面包板的布线。示例的 Fritzing 文件为iot_fritzing_chapter_08_03.fzz,以下图片是面包板视图:

下图显示了用符号表示的电子组件的原理图:

如前图所示,我们在现有项目中添加了以下附加连接:
-
在电路符号中标有5V的电源针与标有+的伺服针相连。伺服通常使用红色电线进行此连接。
-
在电路符号中标有D3 PWM的具有 PWM 功能的 GPIO 针与标有PULSE的伺服针相连。伺服通常使用黄色电线进行此连接。
-
在电路符号中标有GND的接地针与标有-的伺服针相连。伺服通常使用黑色电线进行此连接。
现在,是时候进行所有必要的布线了。在添加或从板上的引脚移除任何电线之前,不要忘记关闭 Yocto Linux,等待所有板载 LED 熄灭,并从 Intel Galileo Gen 2 板上拔掉电源。
使用伺服电机定位轴以指示值
我们可以使用mraa.Pwm类来控制标有~3的具有 PWM 功能的 GPIO 针上的 PWM,正如我们在第四章中学习的,使用 RESTful API 和脉宽调制。然而,这需要我们阅读伺服的详细规格。upm库在pyupm_servo模块中包括了对 SeeedStudio Grove 伺服或 EMAX 9g ES08A 高灵敏度迷你伺服的支持。在此模块中声明的ES08A类代表连接到我们板上的两种提到的伺服器中的任何一个。
该类使得设置伺服轴所需的角度并使用角度而不是占空比和其他 PWM 细节进行工作变得容易。该类在幕后与mraa.Pwm类一起工作以配置 PWM 并根据轴所需的角度控制占空比。
我们将使用之前示例中编写的代码,并将此代码作为添加新功能的基准。示例代码文件为iot_python_chapter_08_02.py。
我们将创建一个TemperatureServo类来表示伺服器,并使我们能够根据华氏度表示的温度将轴定位在有效角度(从 0 到 180 度)内。我们将使用ES08A类与伺服器交互。以下行显示了与upm库一起工作的新TemperatureServo类的代码,特别是与pyupm_servo模块一起工作。示例的代码文件是iot_python_chapter_08_03.py。
import pyupm_th02 as upmTh02
import pyupm_i2clcd as upmLcd
import pyupm_servo as upmServo
import time
class TemperatureServo:
def __init__(self, pin):
self.servo = upmServo.ES08A(pin)
self.servo.setAngle(0)
def print_temperature(self, temperature_fahrenheit):
angle = temperature_fahrenheit
if angle < 0:
angle = 0
elif angle > 180:
angle = 180
self.servo.setAngle(angle)
在创建TemperatureServo类的实例时,我们必须指定与伺服器连接的引脚号,作为pin必需参数。构造函数,即__init__方法,使用接收到的pin作为其pin参数创建一个新的upmServo.ES08A实例,将其引用保存在servo属性中,并调用其setAngle方法,将0作为angle必需参数的值。这样,底层代码将根据angle参数中接收到的值配置 PWM 启用 GPIO 引脚的输出占空比,以将轴定位在所需的角位置。在这种情况下,我们希望轴定位在 0 度。
该类定义了一个print_temperature方法,该方法接收一个以华氏度(ºF)表示的温度值,作为temperature_fahrenheit参数。代码定义了一个angle局部变量,确保所需的轴角度在有效范围内:从 0 到 180 度(包括)。如果temperature_fahrenheit参数中接收到的值低于0,则angle值将为0。如果temperature_fahrenheit参数中接收到的值大于180,则angle值将为180。然后,代码使用angle作为参数调用upmServo.ES08A实例(self.servo)的setAngle方法。在底层,upmServo.ES08A实例将根据angle参数中接收到的值配置 PWM 启用 GPIO 引脚的输出占空比,以将轴定位在所需的角位置。这样,只要温度值在 0 到 180 华氏度(ºF)之间,轴将定位在与其接收到的华氏度(ºF)温度相同的角位置。
如果温度太低(低于 0 华氏度),则轴将保持在 0 度角度。如果温度高于 180 华氏度,则轴将保持在 180 度角度。
现在,我们将修改我们的主循环,每 10 秒显示以华氏度(ºF)表示的环境温度,并用轴表示。示例的代码文件是iot_python_chapter_08_03.py。
if __name__ == "__main__":
temperature_and_humidity_sensor = \
TemperatureAndHumiditySensor(0)
oled = TemperatureAndHumidityOled(0)
temperature_servo = TemperatureServo(3)
while True:
temperature_and_humidity_sensor.\
measure_temperature_and_humidity()
oled.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit,
temperature_and_humidity_sensor.temperature_celsius)
oled.print_humidity(
temperature_and_humidity_sensor.humidity)
temperature_servo.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit)
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor.temperature_fahrenheit))
print("Ambient humidity: {0}".
format(temperature_and_humidity_sensor.humidity))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
突出的行显示了与上一个版本相比对__main__方法所做的更改。第一条突出显示的行使用3作为pin参数的值创建了一个先前编码的TemperatureServo类的实例。代码将此实例的引用保存在temperature_servo局部变量中。这样,该实例将为 3 号引脚配置 PWM 并将轴定位在0度。
然后,代码无限循环运行,突出显示的行调用temperature_servo.print_temperature方法,并将temperature_and_humidity_sensor.temperature_fahrenheit作为参数。这样,代码使轴指向温度计中的温度值。
以下行将开始示例。
python iot_python_chapter_08_03.py
在运行示例后,打开空调或供暖系统并生成环境温度的变化。您将注意到轴每 10 秒开始移动以反映温度的变化。
测试你的知识
-
英特尔 Galileo Gen 2 板可以作为 I²C 总线主控器,并允许我们:
-
只要它们的 I²C 地址不同,就可以将许多从设备连接到 I²C 总线上。
-
只要它们的 I²C 地址相同,就可以将许多从设备连接到 I²C 总线上。
-
只要它们的 I²C 地址不同,就可以将最多两个从设备连接到 I²C 总线上。
-
-
一个 16x2 液晶模块允许我们显示:
-
每行 16 个字符,共两行。
-
每行 16 个字符,每个字符 2 个。
-
每行 16 个字符,每个字符 3 个。
-
-
一个 16 灰度 96x96 点阵 OLED 显示器模块允许我们控制:
-
每行 96 个字符,共 96 行。
-
一行有 96 个点或 96 个字符,具体取决于我们如何配置 OLED 显示器。
-
9,216 个点(96*96)。
-
-
一个 16 灰度 96x96 点阵 OLED 显示器,带有 8x8 字符框,允许我们显示:
-
每行 96 个字符,共 96 行:96 列和 96 行。
-
每行 16 个字符,共 16 行:16 列和 16 行。
-
每行 12 个字符,共 12 行:12 列和 12 行。
-
-
标准伺服允许我们:
-
在 OLED 显示器上显示文本。
-
将轴定位在各个特定角度。
-
通过指定所需的纬度和经度来将轴移动到特定位置。
-
摘要
在本章中,我们学习了我们可以通过 I²C 总线连接到我们的板上的不同显示器。我们使用了一个液晶显示器、一个 RGB 背光,然后将其替换为 OLED 点阵。
我们编写了利用upm库中包含的模块和类来简化我们与 LCD 和 OLED 显示屏以及在其上显示文本的代码。此外,我们还编写了与模拟伺服电机交互的代码。我们不是编写自己的代码来根据轴的期望位置设置输出占空比,而是利用了upm库中的一个特定模块和一个类。我们可以控制轴,以便创建一个仪表图表来显示通过传感器获取的温度值。我们的 Python 代码可以使物体移动。
现在我们能够将数据展示在黑板上并使用伺服电机,我们将把我们的物联网设备连接到整个世界,并使用云服务,这正是下一章的主题。
第九章。与云一起工作
在本章中,我们将利用许多云服务来发布和可视化收集的传感器数据,并在互联网连接的事物之间建立双向通信。我们将涵盖以下主题:
-
使用 dweepy 和
dweet.io将数据发布到云 -
使用 freeboard.io 构建基于网页的仪表板
-
通过 PubNub 在互联网上实时发送和接收数据
-
通过 PubNub 云发布带有命令的消息
-
在物联网设备与其他设备之间进行双向通信
-
使用 Python PubNub 客户端将消息发布到云
-
使用 Mosquitto 和 Eclipse Paho 的 MQTT 协议
-
使用 Python 客户端将消息发布到 Mosquitto 代理
使用 dweepy 将数据发布到云
在第八章中,我们使用数字温度和湿度传感器与显示屏和伺服机构一起工作。现在,我们想利用两个云服务来构建一个实时交互式的基于网页的仪表板,使我们能够在网页浏览器中查看以下信息的仪表盘:
-
环境温度以华氏度(ºF)为单位测量
-
环境温度以摄氏度(ºC)为单位测量
-
环境湿度水平以百分比(%)表示
首先,我们将利用 dweet.io 发布从传感器获取的数据,并使其可供全球的计算机和设备使用。dweet.io 数据共享工具使我们能够轻松发布来自物联网设备的数据或消息和警报,然后使用其他设备订阅这些数据。dweet.io 数据共享工具将自己定义为类似于为社交机器提供的 Twitter。您可以在其网页上了解更多关于 dweet.io 的信息:dweet.io。
小贴士
在我们的示例中,我们将利用 dweet.io 提供的免费服务,而不会使用一些提供数据隐私但需要付费订阅的高级功能。由于我们不使用锁定的 dweets,我们的数据将对任何可以访问 dweet.io 网页的人开放。
dweet.io 数据共享工具提供了一个 Web API,我们可以从我们的物联网设备(在 dweet.io 文档中称为 thing)发送数据。首先,我们必须为我们的 thing 选择一个独特的名称。将字符串与 GUID(即 全球唯一标识符)组合是方便的。另一种选择是点击主 dweet.io 网页上的 立即尝试 按钮,并获取网页为我们的 thing 选择的名字。这样,我们可以确保名字是唯一的,并且没有人使用这个名字为另一个 thing 发布数据到 dweet.io。
一旦我们为我们的设备选择了一个独特的名称,我们就可以开始发布数据,这个过程被称为 dweeting。我们只需要在请求 URL https://dweet.io/dweet/for/my-thing-name 中组合一个 POST HTTP 动词,并在正文中包含所需的 JSON 数据。我们必须将 my-thing-name 替换为我们为设备选择的名称。在我们的示例中,我们将使用 iot_python_chapter_09_01_gaston_hillar 来命名我们的 IoT 设备,该设备将发布温度和湿度值,即将要 dweet 的设备。因此,我们必须在请求 URL https://dweet.io/dweet/for/iot_python_chapter_09_01_gaston_hillar 中组合一个 POST HTTP 动词,并在正文中包含所需的 JSON 数据。确保将名称替换为您为设备选择的名称。
Dweepy 是一个简单的 Python 客户端,用于 dweet.io,它允许我们使用 Python 容易地发布数据到 dweet.io。我们不需要手动使用 Python 构建并发送一个特定的 URL 的 HTTP 请求,而是可以使用这个有用模块提供的方法。以下是为 Dweepy 模块提供的网页:pypi.python.org/pypi/dweepy/0.2.0。在内部,Dweepy 使用流行的 requests 模块提供的流行功能来构建和发送 HTTP 请求。
小贴士
在物联网中作为我们的主要编程语言使用 Python 的一个好处是,总有软件包让事情变得简单。
在 第二章,在英特尔 Galileo Gen 2 上使用 Python 中,我们安装了 pip 安装程序,以便在板上的 Yocto Linux 中轻松安装额外的 Python 2.7.3 软件包。现在,我们将使用 pip 安装程序安装 Dweepy 0.2.0。我们只需要在 SSH 终端中运行以下命令来安装软件包:
pip install dweepy
输出的最后几行将指示 dweepy 软件包已成功安装。不要担心与构建 wheel 相关的错误消息和不安全平台警告:
Collecting dweepy
Downloading dweepy-0.2.0.tar.gz
Requirement already satisfied (use --upgrade to upgrade): requests<3,>=2 in /usr/lib/python2.7/site-packages (from dweepy)
Installing collected packages: dweepy
Running setup.py install for dweepy
Successfully installed dweepy-0.2.0
当我们从传感器读取温度和湿度值时,我们将使用上一章编写的代码,并将此代码作为添加新功能的基线。示例代码文件为 iot_python_chapter_08_03.py。
我们将使用最近安装的 dweepy 模块将数据发布到 dweet.io,并使其作为另一个云服务的数据源,该云服务将允许我们构建基于 Web 的仪表板。我们将在循环中添加必要的行,并且它将每 10 秒发布一次测量的值。示例代码文件为 iot_python_chapter_09_01.py。
import pyupm_th02 as upmTh02
import pyupm_i2clcd as upmLcd
import pyupm_servo as upmServo
import dweepy
import time
if __name__ == "__main__":
temperature_and_humidity_sensor = \
TemperatureAndHumiditySensor(0)
oled = TemperatureAndHumidityOled(0)
temperature_servo = TemperatureServo(3)
# Don't forget to replace the thing_name value
# with your own thing name
thing_name = "iot_python_chapter_09_01_gaston_hillar"
while True:
temperature_and_humidity_sensor.\
measure_temperature_and_humidity()
oled.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit,
temperature_and_humidity_sensor.temperature_celsius)
oled.print_humidity(
temperature_and_humidity_sensor.humidity)
temperature_servo.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit)
# Push data to dweet.io
dweet = {"temperature_celsius": "{:5.2f}".format(temperature_and_humidity_sensor.temperature_celsius),
"temperature_fahrenheit": "{:5.2f}".format(temperature_and_humidity_sensor.temperature_fahrenheit),
"humidity_level_percentage": "{:5.2f}".format(temperature_and_humidity_sensor.humidity)}
dweepy.dweet_for(thing_name, dweet)
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor.temperature_fahrenheit))
print("Ambient humidity: {0}".
format(temperature_and_humidity_sensor.humidity))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
高亮行显示了与上一个版本相比对__main__方法所做的更改。第一条高亮行创建了一个名为thing_name的局部变量,该变量保存了一个字符串,其中包含我们为我们的设备选择的名字,以便与dweet.io一起使用。记住,在运行示例代码之前,你必须将字符串替换为你为设备选择的名字。
然后,代码将无限循环运行,第一条高亮行创建一个字典并将其保存在dweet局部变量中。该字典定义了我们想要作为 JSON 数据发送到dweet.io的键值对。以下是要发送的键:
-
temperature_celsius -
temperature_fahrenheit -
humidity_level_percentage
之前列举的键的值是由传感器获取的值转换为字符串。一旦构建了包含所需 JSON 数据的字典,代码将调用dweepy.dweet_for方法,并将thing_name和dweet作为参数,即设备名称和我们要为指定设备名称发布的 JSON 数据。在幕后,dweepy.dweet_for方法使用requests模块来组合一个 POST HTTP 动词,将dweet字典作为正文中的所需 JSON 数据,并且以下请求 URL:https://dweet.io/dweet/for/后跟在thing_name局部变量中指定的设备名称。这样,代码将传感器获取的温度和湿度值以不同的单位 dweet。
以下行将开始示例。
python iot_python_chapter_09_01.py
在运行示例后,打开空调或加热系统,以产生环境温度和湿度的变化。这样,我们将注意到每 10 秒发布的数据中的变化。
等待大约 20 秒,然后在任何网络浏览器中打开以下 URL:http://dweet.io/follow/iot_python_chapter_09_01_gaston_hillar。别忘了将iot_python_chapter_09_01_gaston_hillar替换为你为你的设备选择的名字。在这种情况下,我们可以在任何连接到互联网的设备上输入此 URL。我们不需要设备与板子处于同一局域网中,因为值是通过dweet.io发布的,并且可以在任何地方访问。
视觉视图将显示一条线形图,显示湿度水平和温度值随时间的变化。右侧将显示最新发布的值。当 Python 代码 dweets 新值时,视图将自动刷新。以下图片显示了带有视觉视图的屏幕截图:

点击原始视图,页面将显示板子上运行的 Python 代码通过dweet.io为我们的设备发布的最新 JSON 数据。以下行显示了之前图片中显示的接收到的最新 JSON 数据示例:
{
"humidity_level_percentage": 20.01,
"temperature_celsius": 19.56,
"temperature_fahrenheit": 67.21
}
在 第四章 中,使用 RESTful API 和脉冲宽度调制,我们安装了 HTTPie,这是一个用 Python 编写的命令行 HTTP 客户端,它使得发送 HTTP 请求变得容易,并且使用的语法比 curl(也称为 cURL)更容易。我们可以在任何计算机或设备上运行以下 HTTPie 命令来检索为我们的事物创建的最新 dweet。
http -b https://dweet.io:443/get/latest/dweet/for/iot_python_chapter_09_01_gaston_hillar
之前的命令将组成并发送以下 HTTP 请求:GET https://dweet.io:443/get/latest/dweet/for/iot_python_chapter_09_01_gaston_hillar。dweet.io API 将返回指定事物的最新 dweet。以下几行显示了 dweet.io 的一个示例响应。JSON 数据包含在 content 键的值中。
{
"by": "getting",
"the": "dweets",
"this": "succeeded",
"with": [
{
"content": {
"humidity_level_percentage": 19.92,
"temperature_celsius": 20.06,
"temperature_fahrenheit": 68.11
},
"created": "2016-03-27T00:11:12.598Z",
"thing": "iot_python_chapter_09_01_gaston_hillar"
}
]
}
我们可以在任何计算机或设备上运行以下 HTTPie 命令来检索我们事物的所有已保存 dweets。
http -b https://dweet.io:443/get/ dweets/for/iot_python_chapter_09_01_gaston_hillar
之前的命令将组成并发送以下 HTTP 请求:GET https://dweet.io:443/get/dweets/for/iot_python_chapter_09_01_gaston_hillar。dweet.io API 将返回指定事物的长期存储中保存的 dweets。以下几行显示了 dweet.io 的一个示例响应。请注意,长期存储中存储的 dweets 数量和返回的值都有限制。
{
"by": "getting",
"the": "dweets",
"this": "succeeded",
"with": [
{
"content": {
"humidity_level_percentage": 19.94,
"temperature_celsius": 20.01,
"temperature_fahrenheit": 68.02
},
"created": "2016-03-27T00:11:00.554Z",
"thing": "iot_python_chapter_09_01_gaston_hillar"
},
{
"content": {
"humidity_level_percentage": 19.92,
"temperature_celsius": 19.98,
"temperature_fahrenheit": 67.96
},
"created": "2016-03-27T00:10:49.823Z",
"thing": "iot_python_chapter_09_01_gaston_hillar"
},
{
"content": {
"humidity_level_percentage": 19.92,
"temperature_celsius": 19.95,
"temperature_fahrenheit": 67.91
},
"created": "2016-03-27T00:10:39.123Z",
"thing": "iot_python_chapter_09_01_gaston_hillar"
},
{
"content": {
"humidity_level_percentage": 19.91,
"temperature_celsius": 19.9,
"temperature_fahrenheit": 67.82
},
"created": "2016-03-27T00:10:28.394Z",
"thing": "iot_python_chapter_09_01_gaston_hillar"
}
]
}
使用 Freeboard 构建 Web 仪表板
dweet.io 数据共享工具使我们能够仅用几行代码轻松地将数据发布到云端。现在,我们准备使用 dweet.io 和我们的设备名称作为数据源来构建一个实时基于网页的仪表板。我们将利用 freeboard.io 来可视化通过传感器收集并发布到 dweet.io 的数据,并在许多仪表中展示仪表板,使其可供世界各地的不同计算机和设备使用。Freeboard.io 将自己定义为一种基于云的服务,允许我们可视化物联网。您可以在其网页上了解更多关于 freeboard.io 的信息:freeboard.io。
小贴士
在我们的示例中,我们将利用 freeboard.io 提供的免费服务,而不会使用一些提供隐私但需要付费订阅的高级功能。由于我们不处理私有仪表板,因此任何拥有其唯一 URL 的人都可以访问我们的仪表板。
Freeboard 要求我们在构建基于网页的仪表板之前注册并使用有效的电子邮件地址和密码创建一个账户。我们不需要输入任何信用卡或支付信息。如果您已经在 freeboard.io 上有账户,您可以跳过下一步。
在您的网络浏览器中访问freeboard.io并点击立即开始。您也可以通过访问freeboard.io/signup达到相同的目的。在选择用户名中输入您想要的用户名,在输入您的电子邮件中输入您的电子邮件,在创建密码中输入您想要的密码。一旦填写完所有字段,点击创建我的账户。
创建账户后,您可以在网络浏览器中访问freeboard.io并点击登录。您也可以通过访问freeboard.io/login达到相同的目的。然后,输入您的用户名或电子邮件和密码,并点击登录。Freeboard 将显示您的免费板,也称为仪表板。
在创建新按钮左侧的输入名称文本框中输入环境温度和湿度,然后点击此按钮。Freeboard.io 将显示一个空白的仪表板,其中包含许多按钮,允许我们添加面板和数据源等。以下图片显示了空白的仪表板截图。

点击数据源下方的添加,网站将打开数据源对话框。在类型下拉菜单中选择Dweet.io,对话框将显示定义dweet.io数据源所需的字段。
在名称中输入环境温度和湿度,在设备名称中输入我们之前用于dweet.io的设备名称。请记住,我们曾使用iot_python_chapter_09_01_gaston_hillar来命名我们的物联网设备,但您已将其替换为不同的名称。如果您输入的名称与您在处理dweet.io时使用的名称不匹配,数据源将不会显示适当的数据。以下图片显示了使用示例设备名称的dweet.io数据源配置的截图。

点击保存,数据源将出现在数据源下方的列表中。由于仪表板正在运行 dweeting 的 Python 代码,因此最后更新下显示的时间将每 10 秒更改一次。如果时间每 10 秒不更改,则意味着数据源配置错误或仪表板不再运行 dweeting 的 Python 代码。
点击添加面板以向仪表板添加一个新的空白面板。然后,点击新空白面板右上角的加号(+),Freeboard 将显示小部件对话框。
在类型下拉菜单中选择量规,对话框将显示添加量规小部件到仪表板面板中所需的字段。在标题中输入华氏温度。
在值文本框的右侧点击+ 数据源,选择环境温度和湿度,然后选择temperature_fahrenheit。在您做出选择后,以下文本将出现在值文本框中:datasources ["Ambient temperature and humidity"] ["temperature_fahrenheit"]。
在单位中输入ºF,在最小值中输入-30,在最大值中输入130。然后,点击保存,Freeboard 将关闭对话框并将新的仪表添加到之前创建的仪表板窗格中。仪表将显示代码在板上最后一次 dweet 的环境温度的最新值,即代码最后发布到dweet.io的 JSON 数据中temperature_fahrenheit键的值。以下图片显示了环境温度和湿度数据源显示的最后更新时间和显示华氏度测量的环境温度的最新值的仪表。

点击添加窗格以向仪表板添加另一个新的空窗格。然后,点击新空窗格右上角的加号(+),Freeboard 将显示小部件对话框。
在类型下拉菜单中选择仪表,对话框将显示添加仪表小部件到仪表板窗格所需的字段。在标题中输入Humidity level in percentage。
在值文本框的右侧点击+ 数据源,选择环境温度和湿度,然后选择humidity_level_percentage。在您做出选择后,以下文本将出现在值文本框中:datasources ["Ambient temperature and humidity"] ["humidity_level_percentage"]。
在单位中输入%,在最小值中输入0,在最大值中输入100。然后,点击保存,Freeboard 将关闭对话框并将新的仪表添加到之前创建的仪表板窗格中。仪表将显示代码在板上最后一次 dweet 的周围湿度水平的最新值,即代码最后发布到dweet.io的 JSON 数据中humidity_level_percentage键的值。以下图片显示了环境温度和湿度数据源显示的最后更新时间和显示华氏度测量的环境温度的最新值的仪表。
现在,点击显示华氏度和 Freeboard 的窗格右上角的加号(+),Freeboard 将显示小部件对话框。
在类型下拉菜单中选择仪表,对话框将显示添加仪表小部件到仪表板窗格所需的字段。在标题中输入Temperature in degrees Celsius。
点击值文本框右侧的+ Datasource,选择环境温度和湿度,然后选择temperature_celsius。在做出选择后,以下文本将出现在值文本框中:datasources ["Ambient temperature and humidity"] ["temperature_celsius"]。
在单位中输入ºC,在最小值中输入-40,在最大值中输入55。然后,点击保存,Freeboard 将关闭对话框并将新的仪表添加到仪表板中之前存在的窗格内。这样,窗格将显示两个仪表,温度以两种不同的单位表示。新的仪表将显示代码在板上 dweeted 的最新值,即环境温度的值,即代码在最后一次发布到 dweet.io 的 JSON 数据中的 temperature_celsius 键的值。
现在,点击显示两个温度的窗格右侧的+按钮旁边的配置图标。Freeboard 将显示窗格对话框。在标题中输入Temperature,然后点击保存。
点击显示湿度水平的窗格右侧的+按钮旁边的配置图标。Freeboard 将显示窗格对话框。在标题中输入Humidity,然后点击保存。
将窗格拖放到位置,将湿度窗格放置在温度窗格的左侧。以下图片显示了我们所构建的仪表板,其中包含两个窗格和三个仪表,当在英特尔 Galileo Gen 2 板上运行的代码 dweets 新数据时,这些仪表会自动刷新数据。

小贴士
我们可以通过输入我们在与仪表板一起工作时网页浏览器显示的 URL 来在任何设备上访问最近构建的仪表板。URL 由 https://freeboard.io/board/ 前缀后跟字母和数字组成。例如,如果 URL 是 https://freeboard.io/board/EXAMPLE,我们只需将其输入到任何设备或计算机上运行的任何网页浏览器中,我们就可以查看仪表,并且当从我们的英特尔 Galileo Gen 2 板向 dweet.io 发布新数据时,它们会刷新。
将 dweet.io 作为我们的数据源和 freeboard.io 作为我们的基于网页的仪表板结合起来,使我们能够轻松地使用任何提供网页浏览器的设备来监控连接到我们的英特尔 Galileo Gen 2 板的传感器获取的数据。这两个基于云的物联网服务的结合只是我们如何轻松结合不同服务的一个例子。物联网云服务的数量正在增加,这些服务可以用于我们的解决方案中。
通过 PubNub 在互联网上实时发送和接收数据
在第四章中,使用 RESTful API 和脉冲宽度调制,我们开发和使用了 RETful API,它允许我们通过 HTTP 请求控制连接到我们的 Intel Galileo Gen 2 板上的电子组件。现在,我们希望通过互联网实时发送和接收数据,而 RESTful API 并不是做这件事的最合适选项。相反,我们将使用基于比 HTTP 协议更轻量级的协议的发布/订阅模型。具体来说,我们将使用基于MQTT(即MQ Telemetry Transport)协议的服务。
MQTT 协议是一种机器到机器(简称M2M)和物联网连接协议。MQTT 是一个轻量级消息协议,它运行在 TCP/IP 协议之上,并使用发布/订阅机制。任何设备都可以订阅特定的频道(也称为主题),并会接收到发布到该频道的所有消息。此外,设备可以向该频道或其他频道发布消息。该协议在物联网和 M2M 项目中变得越来越受欢迎。你可以在以下网页上了解更多关于 MQTT 协议的信息:mqtt.org。
PubNub 提供了许多基于云的服务,其中之一允许我们轻松地实时流数据和向任何设备发送信号,在底层使用 MQTT 协议。我们将利用这个 PubNub 服务通过互联网实时发送和接收数据,并使通过互联网控制我们的 Intel Galileo Gen 2 板变得容易。由于 PubNub 提供了一个具有高质量文档和示例的 Python API,因此使用 Python 来使用该服务非常简单。PubNub 将自己定义为物联网、移动和 Web 应用的全球数据流网络。你可以在其网页上了解更多关于 PubNub 的信息:www.pubnub.com。
小贴士
在我们的示例中,我们将利用 PubNub 提供的免费服务,而不会使用一些可能增强我们的物联网项目连接需求但需要付费订阅的高级功能和附加服务。
PubNub 要求我们在创建应用程序之前先注册并使用有效的电子邮件和密码创建一个账户,该应用程序允许我们开始使用他们的免费服务。我们不需要输入任何信用卡或支付信息。如果你已经在 PubNub 有账户,你可以跳过下一步。
一旦你创建了账户,PubNub 将把你重定向到管理门户,该门户列出了你的 PubNub 应用程序。为了在网络上发送和接收消息,你需要生成你的 PubNub 发布和订阅密钥。管理门户中的一个新面板将代表应用程序。以下截图显示了 PubNub 管理门户中的温度控制应用程序面板:

单击温度控制面板,PubNub 将显示为应用程序自动生成的演示密钥集面板。单击此面板,PubNub 将显示发布、订阅和密钥。我们必须复制并粘贴这些密钥中的每一个,以便在我们的代码中使用它们来发布消息并订阅它们。以下截图显示了密钥的前缀和图像中已擦除的剩余字符:

为了复制密钥,您必须单击密钥右侧的眼睛图标,PubNub 将使所有字符可见。
在第二章,在英特尔 Galileo Gen 2 上使用 Python中,我们安装了pip安装程序,以便在板子上运行的 Yocto Linux 中轻松安装额外的 Python 2.7.3 软件包。现在,我们将使用pip安装程序安装 PubNub Python SDK 3.7.6。我们只需在 SSH 终端中运行以下命令即可安装该软件包。请注意,安装可能需要几分钟时间。
pip install pubnub
输出的最后几行将指示pubnub软件包已成功安装。不要担心与构建 wheel 相关的错误消息和不安全平台警告。
Downloading pubnub-3.7.6.tar.gz
Collecting pycrypto>=2.6.1 (from pubnub)
Downloading pycrypto-2.6.1.tar.gz (446kB)
100% |################################| 446kB 25kB/s
Requirement already satisfied (use --upgrade to upgrade): requests>=2.4.0 in /usr/lib/python2.7/site-packages (from pubnub)
Installing collected packages: pycrypto, pubnub
Running setup.py install for pycrypto
Installing collected packages: pycrypto, pubnub
Running setup.py install for pycrypto
Running setup.py install for pubnub
Successfully installed pubnub-3.7.6 pycrypto-2.6.1
我们将使用我们在上一章中编写的代码,当时我们从传感器读取温度和湿度值,我们在 OLED 矩阵中打印了这些值,并通过旋转伺服电机的轴来显示以华氏度表示的测量温度。示例代码文件为iot_python_chapter_08_03.py。我们将以此代码为基础,添加新功能,使我们能够执行以下操作,使用 PubNub 消息发送到特定通道的任何具有网络浏览器的设备:
-
通过旋转伺服电机的轴来显示作为消息一部分接收到的华氏温度值。
-
在 OLED 矩阵的底部显示作为消息一部分接收到的文本行。
我们将使用最近安装的 pubnub 模块来订阅特定频道,并在接收到该频道上的消息时运行代码。我们将创建一个 MessageChannel 类来表示通信通道,配置 PubNub 订阅并声明当某些事件被触发时要执行的回调代码。示例代码文件为 iot_python_chapter_09_02.py。请记住,我们使用代码文件 iot_python_chapter_08_03.py 作为基线,因此,我们将把类添加到现有代码文件中,并创建一个新的 Python 文件。不要忘记将 __init__ 方法中分配给 publish_key 和 subscribe_key 局部变量的字符串替换为从之前解释的 PubNub 密钥生成过程中检索到的值。
import time
from pubnub import Pubnub
class MessageChannel:
command_key = "command"
def __init__(self, channel, temperature_servo, oled):
self.temperature_servo = temperature_servo
self.oled = oled
self.channel = channel
# Publish key is the one that usually starts with the "pub-c-" prefix
# Do not forget to replace the string with your publish key
publish_key = "pub-c-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Subscribe key is the one that usually starts with the "sub-c" prefix
# Do not forget to replace the string with your subscribe key
subscribe_key = "sub-c-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
self.pubnub = Pubnub(publish_key=publish_key, subscribe_key=subscribe_key)
self.pubnub.subscribe(channels=self.channel,
callback=self.callback,
error=self.callback,
connect=self.connect,
reconnect=self.reconnect,
disconnect=self.disconnect)
def callback(self, message, channel):
if channel == self.channel:
if self.__class__.command_key in message:
if message[self.__class__.command_key] == "print_temperature_fahrenheit":
self.temperature_servo.print_temperature(message["temperature_fahrenheit"])
elif message[self.__class__.command_key] == "print_information_message":
self.oled.print_line(11, message["text"])
print("I've received the following message: {0}".format(message))
def error(self, message):
print("Error: " + str(message))
def connect(self, message):
print("Connected to the {0} channel".
format(self.channel))
print(self.pubnub.publish(
channel=self.channel,
message="Listening to messages in the Intel Galileo Gen 2 board"))
def reconnect(self, message):
print("Reconnected to the {0} channel".
format(self.channel))
def disconnect(self, message):
print("Disconnected from the {0} channel".
Format(self.channel))
MessageChannel 类声明了 command_key 类属性,它定义了代码将理解为何种命令的关键字符串。每当收到包含指定关键字符串的消息时,我们知道与该键关联的字典中的值将指示消息希望代码在板上处理的命令。每个命令都需要额外的键值对,以提供执行命令所需的信息。
我们必须指定 PubNub 频道名称,channel 中的 TemperatureServo 实例,Oled 实例所需的 temperature_servo 和 oled 参数。构造函数,即 __init__ 方法,将接收到的参数保存在具有相同名称的三个属性中。channel 参数指定了我们将订阅以监听发送到该频道的消息的 PubNub 频道。我们还将向该频道发布消息,因此,我们将成为该频道的订阅者和发布者。
小贴士
在这种情况下,我们只会订阅一个频道。然而,非常重要的一点是,我们并不仅限于订阅单个频道,我们可能订阅多个频道。
然后,构造函数声明了两个局部变量:publish_key 和 subscribe_key。这些局部变量保存了我们使用 PubNub 管理门户生成的发布和订阅密钥。然后,代码使用 publish_key 和 subscribe_key 作为参数创建一个新的 Pubnub 实例,并将新实例的引用保存在 pubnub 属性中。最后,代码调用新实例的 subscribe 方法来订阅保存在 channel 属性中的频道上的数据。在底层,subscribe 方法使客户端创建一个到包含 MQTT 代理的 PubNub 网络的开放 TCP 套接字,并开始监听指定频道上的消息。对这个方法的调用指定了在 MessageChannel 类中声明的许多方法,以下为命名参数:
-
callback:指定在从通道接收到新消息时将被调用的函数 -
error:指定在错误事件上将被调用的函数 -
connect:指定当与 PubNub 云成功建立连接时将被调用的函数 -
reconnect:指定当与 PubNub 云成功重新连接完成后将被调用的函数 -
disconnect:指定当客户端从 PubNub 云断开连接时将被调用的函数
这样,每当之前列举的事件之一发生时,指定的方法将被执行。callback方法接收两个参数:message和channel。首先,该方法检查接收到的channel是否与channel属性中的值匹配。在这种情况下,每当callback方法执行时,channel参数中的值将始终与channel属性中的值匹配,因为我们刚刚订阅了一个通道。然而,如果我们订阅了多个通道,则始终有必要检查消息是在哪个通道中发送的,以及我们在哪个通道接收消息。
然后,代码检查command_key类属性是否包含在message字典中。如果表达式评估为True,则表示消息包含我们必须处理的命令。然而,在我们能够处理该命令之前,我们必须检查是哪个命令,因此,有必要检索与command_key类属性等效的键的值。当值是以下两个命令中的任何一个时,代码能够执行代码:
-
print_temperature_fahrenheit:该命令必须在temperature_fahrenheit键的值中指定以华氏度表示的温度值。代码使用从字典中检索到的温度值作为参数调用self.temperature_servo.print_temperature方法。这样,代码就根据消息中包含该命令指定的温度值移动伺服电机的轴。 -
print_information_message:该命令必须在print_information_message键的值中指定要在 OLED 矩阵底部显示的文本行。代码使用self.oled.print_line方法,并带有11和从字典中检索到的文本值作为参数调用。这样,代码就在 OLED 矩阵的底部显示了包含该命令的消息中接收到的文本。
无论消息是否包含有效的命令,该方法都会在控制台输出中打印它接收到的原始消息。
connect 方法打印一条消息,表明已与通道建立了连接。然后,该方法打印调用 self.pubnub.publish 方法的结果,该方法在 self.channel 保存的通道名称中发布消息:"Listening to messages in the Intel Galileo Gen 2 board"。在这种情况下,对该方法的调用是同步执行的。我们将在下一个示例中为此方法使用异步执行。
在此时,我们已订阅此通道,因此,我们将接收到之前发布的消息,并且回调方法将使用此消息作为参数执行。然而,由于消息不包含标识命令的密钥,回调方法中的代码将仅显示接收到的消息,而不会处理之前分析过的任何命令。
在 MessageChannel 类中声明的其他方法只是将事件发生的信息显示到控制台输出。
现在,我们将使用之前编写的 MessageChannel 类来创建一个新版本的 __main__ 方法,该方法使用 PubNub 云接收和处理命令。新版本在环境温度变化时不会旋转伺服电机的轴,相反,它将在接收到来自连接到 PubNub 云的任何设备的适当命令时执行此操作。以下行显示了 __main__ 方法的新版本。示例的代码文件为 iot_python_chapter_09_02.py。
if __name__ == "__main__":
temperature_and_humidity_sensor = \
TemperatureAndHumiditySensor(0)
oled = TemperatureAndHumidityOled(0)
temperature_servo = TemperatureServo(3)
message_channel = MessageChannel("temperature", temperature_servo, oled)
while True:
temperature_and_humidity_sensor.\
measure_temperature_and_humidity()
oled.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit,
temperature_and_humidity_sensor.temperature_celsius)
oled.print_humidity(
temperature_and_humidity_sensor.humidity)
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor.temperature_fahrenheit))
print("Ambient humidity: {0}".
format(temperature_and_humidity_sensor.humidity))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
突出的行创建了一个之前编写的 MessageChannel 类的实例,参数为 "temperature"、temperature_servo 和 oled。构造函数将订阅 PubNub 云中的 temperature 通道,因此,我们必须向此通道发送消息,以便发送代码将异步执行的命令。循环将读取传感器的值并将值打印到控制台,就像代码的先前版本一样,因此,我们将在循环中运行代码,同时我们也将有代码在 PubNub 云的 temperature 通道中监听消息。我们将在稍后开始示例,因为我们想在板上运行代码之前,在 PubNub 调试控制台中订阅通道。
通过 PubNub 云发布带有命令的消息
现在,我们将利用 PubNub 控制台向 temperature 通道发送带有命令的消息,并使板上的 Python 代码处理这些命令。如果你已经从 PubNub 登出,请重新登录并点击 Admin Portal 中的 Temperature Control 面板。PubNub 将显示 Demo Keyset 面板。
点击 Demo Keyset 面板,PubNub 将显示发布、订阅和密钥。这样,我们选择我们想要用于我们的 PubNub 应用的密钥集。
点击屏幕左侧侧边栏上的 调试控制台。PubNub 将为默认通道创建一个客户端,并使用我们在上一步中选择的密钥订阅此通道。我们想订阅 temperature 通道,因此,在包含 添加客户端 按钮的面板中的 默认通道 文本框中输入 temperature。然后,点击 添加客户端,PubNub 将添加一个带有随机客户端名称的新的面板,第二行是通道名称 temperature。PubNub 使客户端订阅此通道,我们就能接收发布到此通道的消息,并向此通道发送消息。以下图片显示了名为 Client-ot7pi 的生成客户端的面板,已订阅 temperature 通道。注意,当您按照解释的步骤操作时,客户端名称将不同。

客户端面板显示了 PubNub 订阅客户端到通道时生成的输出。PubNub 为每个命令返回一个格式化的响应。在这种情况下,它表示状态等于 Subscribed,通道名称为 temperature。
[1,"Subscribed","temperature"]
现在,是时候在英特尔 Galileo Gen 2 板上运行示例了。以下行将在 SSH 控制台中启动示例:
python iot_python_chapter_09_02.py
运行示例后,转到您正在使用 PubNub 调试控制台的 Web 浏览器。您将看到以下消息在之前创建的客户端中列出:
"Listening to messages in the Intel Galileo Gen 2 board"
在板上运行的 Python 代码发布了这条消息,具体来说,是 MessageChannel 类中的 connect 方法在应用程序与 PubNub 云建立连接后发送了这条消息。下面的图片显示了之前创建的客户端中列出的消息。注意,文本左侧的图标表示这是一条消息。另一条消息是一个调试消息,包含了订阅通道的结果。

在客户端面板的底部,您将看到以下文本和位于右侧的 发送 按钮:
{"text":"Enter Message Here"}
现在,我们将用一条消息替换之前显示的文本。输入以下 JSON 代码并点击 发送:
{"command":"print_temperature_fahrenheit", "temperature_fahrenheit": 50 }
小贴士
在其中输入消息的文本编辑器在某些浏览器中存在一些问题。因此,使用您喜欢的文本编辑器输入 JSON 代码,复制它,然后将其粘贴以替换消息文本中默认包含的文本是方便的。
点击发送后,客户端日志中会出现以下几行。第一行是一个包含发布消息结果的调试消息,并表明消息已被发送。格式化响应包括一个数字(1条消息)、状态(Sent)和时间戳。第二行是到达通道的消息,因为我们订阅了temperature通道,也就是说,我们也收到了我们发送的消息。
[1,"Sent","14594756860875537"]
{
"command": "print_temperature_fahrenheit",
"temperature_fahrenheit": 50
}
以下图片展示了点击发送按钮后 PubNub 客户端的消息和调试消息日志:

在发布上一条消息后,你将在 Intel Galileo Gen 2 板的 SSH 控制台中看到以下输出。你会注意到伺服电机的轴旋转到 50 度。
I've received the following message: {u'command': u'print_temperature_fahrenheit', u'temperature_fahrenheit': 50}
现在,输入以下 JSON 代码并点击发送:
{"command":"print_information_message", "text": "Client ready"}
点击发送后,客户端日志中会出现以下几行。第一行是一个调试消息,包含之前解释过的格式化响应,显示了发布消息的结果,并表明消息已被发送。格式化响应包括一个数字(1条消息)、状态(Sent)和时间戳。第二行是到达通道的消息,因为我们订阅了temperature通道,也就是说,我们也收到了我们发送的消息。
[1,"Sent","14594794434885921"]
{
"command": "print_information_message",
"text": "Client ready"
}
以下图片展示了点击发送按钮后 PubNub 客户端的消息和调试消息日志。

发布上一条消息后,你将在 Intel Galileo Gen 2 板的 SSH 控制台中看到以下输出。你将在 OLED 矩阵的底部看到以下文本:Client ready。
I've received the following message: {u'text': u'Client ready', u'command': u'print_information_message'}
当我们使用命令发布了两条消息时,我们肯定注意到了一个问题。我们不知道在运行在物联网设备上的代码(即 Intel Galileo Gen 2 板)中,命令是否被处理。我们知道板子已经开始监听温度通道的消息,但在命令处理完毕后,我们没有从物联网设备收到任何类型的响应。
处理双向通信
我们可以轻松地添加几行代码,在接收消息的同一条通道上发布消息,以指示命令已成功处理。我们将使用之前的示例作为基准,并创建MessageChannel类的新版本。代码文件是iot_python_chapter_09_02.py。别忘了在__init__方法中将分配给publish_key和subscribe_key局部变量的字符串替换为之前解释过的 PubNub 密钥生成过程中检索到的值。以下几行展示了发布消息后命令已成功处理的MessageChannel类的新版本。示例代码文件是iot_python_chapter_09_03.py。
import time
from pubnub import Pubnub
class MessageChannel:
command_key = "command"
successfully_processed_command_key = "successfully_processed_command"
def __init__(self, channel, temperature_servo, oled):
self.temperature_servo = temperature_servo
self.oled = oled
self.channel = channel
# Do not forget to replace the string with your publish key
publish_key = "pub-c-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Subscribe key is the one that usually starts with the "sub-c" prefix
# Do not forget to replace the string with your subscribe key
subscribe_key = "sub-c-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
self.pubnub = Pubnub(publish_key=publish_key, subscribe_key=subscribe_key)
self.pubnub.subscribe(channels=self.channel,
callback=self.callback,
error=self.callback,
connect=self.connect,
reconnect=self.reconnect,
disconnect=self.disconnect)
def callback_response_message(self, message):
print("I've received the following response from PubNub cloud: {0}".format(message))
def error_response_message(self, message):
print("There was an error when working with the PubNub cloud: {0}".format(message))
def publish_response_message(self, message):
response_message = {
self.__class__.successfully_processed_command_key:
message[self.__class__.command_key]}
self.pubnub.publish(
channel=self.channel,
message=response_message,
callback=self.callback_response_message,
error=self.error_response_message)
def callback(self, message, channel):
if channel == self.channel:
print("I've received the following message: {0}".format(message))
if self.__class__.command_key in message:
if message[self.__class__.command_key] == "print_temperature_fahrenheit":
self.temperature_servo.print_temperature(message["temperature_fahrenheit"])
self.publish_response_message(message)
elif message[self.__class__.command_key] == "print_information_message":
self.oled.print_line(11, message["text"])
self.publish_response_message(message)
def error(self, message):
print("Error: " + str(message))
def connect(self, message):
print("Connected to the {0} channel".
format(self.channel))
print(self.pubnub.publish(
channel=self.channel,
message="Listening to messages in the Intel Galileo Gen 2 board"))
def reconnect(self, message):
print("Reconnected to the {0} channel".
format(self.channel))
def disconnect(self, message):
print("Disconnected from the {0} channel".
format(self.channel))
之前代码中突出显示的MessageChannel类新版本的代码行显示了我们对代码所做的更改。首先,代码声明了successfully_processed_command_key类属性,该属性定义了代码将在发布到通道的响应消息中使用的关键字符串,作为成功处理的命令键。每次我们发布包含指定键字符串的消息时,我们知道与该键关联的字典中的值将指示板已成功处理的命令。
代码声明了以下三个新方法:
-
callback_response_message:此方法将用作在成功处理的命令响应消息发布到通道时执行的回调。该方法仅打印 PubNub 在通道中成功发布消息时返回的格式化响应。在这种情况下,message参数不包含已发布的原始消息,而是包含格式化响应。我们使用message作为参数名称以保持与 PubNub API 的一致性。 -
error_response_message:此方法将用作在尝试将成功处理的命令响应消息发布到通道时发生错误时执行的回调。该方法仅打印 PubNub 在通道中未成功发布消息时返回的错误消息。 -
publish_response_message:此方法接收包含在message参数中成功处理的命令的消息。代码创建一个response_message字典,其中successfully_processed_command_key类属性作为键,消息字典中command_key类属性指定的键的值作为值。然后,代码调用self.pubnub.publish方法将response_message字典发布到存储在channel属性中的通道。对此方法的调用指定self.callback_response_message作为在消息成功发布时执行的回调,以及self.error_response_message作为在发布过程中发生错误时执行的回调。当我们指定回调时,发布方法以异步执行方式工作,因此执行是非阻塞的。消息的发布和指定的回调将在不同的线程中运行。
现在,在MessageChannel类中定义的callback方法向publish_response_message方法添加了一个调用,该调用以包含已成功处理命令的消息(message)作为参数。正如之前解释的那样,publish_response_message方法是非阻塞的,并且将在另一个线程中发布成功处理的消息时立即返回。
现在,是时候在 Intel Galileo Gen 2 板上运行示例了。以下行将在 SSH 控制台中启动示例:
python iot_python_chapter_09_03.py
运行示例后,转到您与 PubNub 调试控制台一起工作的 Web 浏览器。您将在之前创建的客户中看到以下消息:
"Listening to messages in the Intel Galileo Gen 2 board"
输入以下 JSON 代码并点击发送:
{"command":"print_temperature_fahrenheit", "temperature_fahrenheit": 90 }
点击发送后,客户端日志中会出现以下行。最后一条消息是由板发布到频道的,表示 print_temperature_fahrenheit 命令已成功处理。
[1,"Sent","14595406989121047"]
{
"command": "print_temperature_fahrenheit",
"temperature_fahrenheit": 90
}
{
"successfully_processed_command": "print_temperature_fahrenheit"
}
以下图片展示了点击发送按钮后 PubNub 客户端的消息和调试消息日志:

发布之前的消息后,您将在 Intel Galileo Gen 2 板的 SSH 控制台中看到以下输出。您会注意到伺服电机的轴旋转到 90 度。该板还接收到了成功处理的命令消息,因为它订阅了发布消息的频道。
I've received the following message: {u'command': u'print_temperature_fahrenheit', u'temperature_fahrenheit': 90}
I've received the following response from PubNub cloud: [1, u'Sent', u'14595422426124592']
I've received the following message: {u'successfully_processed_command': u'print_temperature_fahrenheit'}
现在,输入以下 JSON 代码并点击发送:
{"command":"print_information_message", "text": "2nd message"}
点击发送后,客户端日志中会出现以下行。最后一条消息是由板发布到频道的,表示 print_information_message 命令已成功处理。
[1,"Sent","14595434708640961"]
{
"command": "print_information_message",
"text": "2nd message"
}
{
"successfully_processed_command": "print_information_message"
}
以下图片展示了点击发送按钮后 PubNub 客户端的消息和调试消息日志。

发布之前的消息后,您将在 Intel Galileo Gen 2 板的 SSH 控制台中看到以下输出。您将在 OLED 矩阵的底部看到以下文本:2nd message。该板还接收到了成功处理的命令消息,因为它订阅了发布消息的频道。
I've received the following message: {u'text': u'2nd message', u'command': u'print_information_message'}
2nd message
I've received the following response from PubNub cloud: [1, u'Sent', u'14595434710438777']
I've received the following message: {u'successfully_processed_command': u'print_information_message'}
我们可以使用 PubNub 提供的不同 SDK 订阅和发布到频道。我们还可以通过向频道发布消息并处理它们,使不同的 IoT 设备相互通信。在这种情况下,我们只创建了一些命令,并没有添加关于必须处理命令的设备或生成特定消息的设备的详细信息。更复杂的 API 需要包含更多信息和安全性的命令。
使用 Python PubNub 客户端向云端发布消息
到目前为止,我们一直在使用 PubNub 调试控制台向 temperature 频道发布消息,并让 Intel Galileo Gen 2 板上的 Python 代码处理它们。现在,我们将编写一个 Python 客户端,它将向 temperature 频道发布消息。这样,我们将能够设计能够与 IoT 设备通信的应用程序,发布者端和订阅者设备端都使用 Python 代码。
我们可以在另一个 Intel Galileo Gen 2 板上或在安装了 Python 2.7.x 的任何设备上运行 Python 客户端。此外,代码将以 Python 3.x 运行。例如,我们可以在我们的计算机上运行 Python 客户端。我们只需确保在板上的 Yocto Linux 运行的 Python 版本中安装了之前用 pip 安装的 pubnub 模块。
我们将创建一个 Client 类来表示 PubNub 客户端,配置 PubNub 订阅,使其能够轻松发布带有命令和所需值的消息,并声明当某些事件触发时要执行的回调代码。示例代码文件为 iot_python_chapter_09_04.py。不要忘记将 __init__ 方法中分配给 publish_key 和 subscribe_key 局部变量的字符串替换为从之前解释的 PubNub 密钥生成过程中检索到的值。以下行显示了 Client 类的代码:
import time
from pubnub import Pubnub
class Client:
command_key = "command"
def __init__(self, channel):
self.channel = channel
# Publish key is the one that usually starts with the "pub-c-" prefix
publish_key = "pub-c-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Subscribe key is the one that usually starts with the "sub-c" prefix
# Do not forget to replace the string with your subscribe key
subscribe_key = "sub-c-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
self.pubnub = Pubnub(publish_key=publish_key, subscribe_key=subscribe_key)
self.pubnub.subscribe(channels=self.channel,
callback=self.callback,
error=self.callback,
connect=self.connect,
reconnect=self.reconnect,
disconnect=self.disconnect)
def callback_command_message(self, message):
print("I've received the following response from PubNub cloud: {0}".format(message))
def error_command_message(self, message):
print("There was an error when working with the PubNub cloud: {0}".format(message))
def publish_command(self, command_name, key, value):
command_message = {
self.__class__.command_key: command_name,
key: value}
self.pubnub.publish(
channel=self.channel,
message=command_message,
callback=self.callback_command_message,
error=self.error_command_message)
def callback(self, message, channel):
if channel == self.channel:
print("I've received the following message: {0}".format(message))
def error(self, message):
print("Error: " + str(message))
def connect(self, message):
print("Connected to the {0} channel".
format(self.channel))
print(self.pubnub.publish(
channel=self.channel,
message="Listening to messages in the PubNub Python Client"))
def reconnect(self, message):
print("Reconnected to the {0} channel".
format(self.channel))
def disconnect(self, message):
print("Disconnected from the {0} channel".
format(self.channel))
Client 类声明了 command_key 类属性,该属性定义了代码在消息中理解为何种命令的键字符串。我们的主要目标是构建并发布到指定频道的命令消息。我们必须在 channel 所需参数中指定 PubNub 频道名称。构造函数,即 __init__ 方法,将接收到的参数保存在具有相同名称的属性中。我们将成为此频道的订阅者和发布者。
然后,构造函数声明了两个局部变量:publish_key 和 subscribe_key。这些局部变量保存了我们使用 PubNub 管理门户生成的发布和订阅密钥。然后,代码使用 publish_key 和 subscribe_key 作为参数创建一个新的 Pubnub 实例,并将新实例的引用保存在 pubnub 属性中。最后,代码调用新实例的 subscribe 方法来订阅保存在 channel 属性中的频道上的数据。对这个方法的调用指定了许多在 Client 类中声明的函数,就像我们在之前的示例中所做的那样。
publish_command 方法接收一个命令名称、键和值,这些键和值提供了执行 command_name、key 和 value 所需的必要信息。在这种情况下,我们没有将命令针对特定的物联网设备,而是所有订阅该频道并在我们之前的示例中运行代码的设备都将处理我们发布的命令。我们可以使用此代码作为基准,以处理更复杂的示例,在这些示例中,我们必须生成针对特定物联网设备的命令。显然,提高安全性也是必要的。
该方法创建一个字典,并将其保存在command_message局部变量中。command_key类属性是字典的第一个键,而command_name作为参数接收的值,是构成第一个键值对的值。然后,代码调用self.pubnub.publish方法,将command_message字典发布到保存在channel属性中的频道。对这个方法的调用指定了self.callback_command_message作为在消息成功发布时执行的回调,以及self.error_command_message作为在发布过程中发生错误时执行的回调。正如我们之前的例子中发生的那样,当我们指定回调时,publish方法将以异步执行的方式工作。
现在,我们将使用之前编写的Client类来编写一个__main__方法,该方法使用 PubNub 云发布两个命令,我们的板子将处理这些命令。以下行显示了__main__方法的代码。示例的代码文件是iot_python_chapter_09_04.py。
if __name__ == "__main__":
client = Client("temperature")
client.publish_command(
"print_temperature_fahrenheit",
"temperature_fahrenheit",
45)
client.publish_command(
"print_information_message",
"text",
"Python IoT"
)
# Sleep 60 seconds (60000 milliseconds)
time.sleep(60)
__main__方法中的代码非常容易理解。代码使用"temperature"作为参数创建Client类的一个实例,使其成为 PubNub 云中该频道的订阅者和发布者。代码将新实例保存在client局部变量中。
代码调用publish_command方法,并传入必要的参数来构建和发布print_temperature_fahrenheit命令,该命令的温度值为45。该方法将以异步执行的方式发布命令。然后,代码再次调用publish_command方法,并传入必要的参数来构建和发布print_information_message命令,该命令的文本值为"Python IoT"。该方法将以异步执行的方式发布第二个命令。
最后,代码暂停 1 分钟(60 秒),以便异步执行成功发布命令。在Client类中定义的不同回调将在不同事件触发时执行。由于我们也订阅了该频道,我们还将收到在temperature频道发布的消息。
保持我们在之前的例子中执行的 Python 代码在板上运行。我们希望板子处理我们的命令。此外,保持您正在使用的 Web 浏览器和 PubNub 调试控制台打开,因为我们还希望看到日志中的所有消息。
以下行将在任何您想要用作客户端的计算机或设备上启动 Python 客户端的示例。如果您想使用同一块板作为客户端,可以在另一个 SSH 终端中运行代码。
python iot_python_chapter_09_04.py
运行示例后,您将在运行 Python 客户端的 Python 控制台中看到以下输出,即iot_python_chapter_09_04.py Python 脚本。
Connected to the temperature channel
I've received the following response from PubNub cloud: [1, u'Sent', u'14596508980494876']
I've received the following response from PubNub cloud: [1, u'Sent', u'14596508980505581']
[1, u'Sent', u'14596508982165140']
I've received the following message: {u'text': u'Python IoT', u'command': u'print_information_message'}
I've received the following message: {u'command': u'print_temperature_fahrenheit', u'temperature_fahrenheit': 45}
I've received the following message: Listening to messages in the PubNub Python Client
I've received the following message: {u'successfully_processed_command': u'print_information_message'}
I've received the following message: {u'successfully_processed_command': u'print_temperature_fahrenheit'}
代码使用了 PubNub Python SDK 构建,并在 temperature 通道中发布了以下两个命令消息:
{"command":"print_temperature_fahrenheit", "temperature_fahrenheit": "45"}
{"command":"print_information_message", "text": "Python IoT"}
由于我们也订阅了温度通道,我们以异步方式接收我们发送的消息。然后,我们收到了成功处理的两个命令消息。板子已经处理了命令,并将消息发布到了 temperature 通道。
在运行示例后,转到您正在使用 PubNub 调试控制台工作的网络浏览器。您将看到以下消息列在之前创建的客户端中:
[1,"Subscribed","temperature"]
"Listening to messages in the Intel Galileo Gen 2 board"
{
"text": "Python IoT",
"command": "print_information_message"
}
{
"command": "print_temperature_fahrenheit",
"temperature_fahrenheit": 45
}
"Listening to messages in the PubNub Python Client"
{
"successfully_processed_command": "print_information_message"
}
{
"successfully_processed_command": "print_temperature_fahrenheit"
}
以下图片显示了在运行上一个示例后 PubNub 客户端日志中显示的最后一条消息:

您将在 OLED 矩阵的底部看到以下文本:Python IoT。此外,伺服电机的轴将旋转到 45 度。
提示
我们可以使用不同编程语言中可用的 PubNub SDK 来创建应用程序和应用程序,这些应用程序可以在 PubNub 云中发布和接收消息,并与物联网设备交互。在这种情况下,我们与 Python SDK 合作创建了一个发布命令的客户端。可以创建发布命令的移动应用程序,并轻松构建一个可以与我们的物联网设备交互的应用程序。
使用 Mosquitto 和 Eclipse Paho 的 MQTT
Mosquitto 是一个开源的消息代理,实现了 MQTT 协议的 3.1 和 3.1.1 版本,因此,允许我们使用发布/订阅模型来处理消息。Mosquitto 是 iot.eclipse.org 项目,并提供了 Eclipse 公共项目 (EPL)/EDL 许可证。以下是为 Mosquitto 提供的网页:mosquitto.org。
Eclipse Paho 项目提供了一个开源的 MQTT 客户端实现。该项目包括一个 Python 客户端,也称为 Paho Python 客户端或 Eclipse Paho MQTT Python 客户端库。这个 Python 客户端是由 Mosquitto 项目贡献的,最初是 Mosquitto Python 客户端。以下是为 Eclipse Paho 项目提供的网页:www.eclipse.org/paho。以下是为 Eclipse Paho MQTT Python 客户端库提供的网页,即 paho-mqtt 模块:pypi.python.org/pypi/paho-mqtt/1.1。
在 第二章,在英特尔 Galileo Gen 2 上使用 Python,我们安装了 pip 安装程序,以便在板子上运行的 Yocto Linux 中轻松安装额外的 Python 2.7.3 软件包。现在,我们将使用 pip 安装程序安装 paho-mqtt 1.1。我们只需要在 SSH 终端中运行以下命令来安装该软件包。
pip install paho-mqtt
输出的最后几行将指示 paho-mqtt 包已成功安装。不要担心与构建 wheel 相关的错误消息和不安全平台警告。
Collecting paho-mqtt
Downloading paho-mqtt-1.1.tar.gz (41kB)
100% |################################| 45kB 147kB/s
Installing collected packages: paho-mqtt
Running setup.py install for paho-mqtt
Successfully installed paho-mqtt-1.1
小贴士
Eclipse 允许我们使用一个公开可访问的沙盒服务器来运行 Eclipse IoT 项目,该服务器位于 iot.eclipse.org,端口号为 1883。在接下来的示例中,我们将使用这个沙盒服务器作为我们的 Mosquitto 消息代理。这样,我们就不需要设置一个 Mosquitto 消息代理来测试示例并学习如何使用 Paho Python 客户端。然而,在实际应用中,我们应该设置一个 Mosquitto 消息代理并用于我们的项目。
当我们从传感器读取温度和湿度值时,我们在上一章中编写的代码,我们将值打印在 OLED 矩阵上,并旋转伺服电机的轴来显示以华氏度表示的测量温度。该示例的代码文件为 iot_python_chapter_08_03.py。我们将使用此代码作为基准,添加我们在与 PubNub 云一起工作时添加的相同功能。然而,在这种情况下,我们将使用 Paho Python 客户端和公开可访问的沙盒服务器,该服务器为我们提供了一个 Mosquitto 消息代理。我们将能够执行以下操作,通过将 MQTT 消息发送到特定主题,从任何可以发布 MQTT 消息到我们订阅的主题的设备:
-
旋转伺服电机的轴以显示作为消息一部分接收到的华氏度温度值
-
在 OLED 矩阵的底部显示作为消息一部分接收到的文本行
小贴士
Paho Python 客户端使用主题名称而不是通道。你可以将主题视为一个通道。
我们将使用最近安装的 paho-mqtt 模块来订阅特定主题,并在接收到主题中的消息时运行代码。我们将创建一个 MessageTopic 类来表示通信主题,配置 MQTT 客户端、客户端的订阅以及声明当某些事件触发时要执行的回调代码。该示例的代码文件为 iot_python_chapter_09_05.py。请记住,我们使用代码文件 iot_python_chapter_08_03.py 作为基准,因此,我们将向该文件中现有的代码添加类,并创建一个新的 Python 文件。不要忘记将分配给 topic 类属性的字符串替换为你的唯一主题名称。由于我们使用的 Mosquitto 代理是公开的,你应该使用一个唯一主题,以确保你只接收你发布的消息。
import time
import paho.mqtt.client as mqtt
import json
class MessageTopic:
command_key = "command"
successfully_processed_command_key = "successfully_processed_command"
# Replace with your own topic name
topic = "iot-python-gaston-hillar/temperature"
active_instance = None
def __init__(self, temperature_servo, oled):
self.temperature_servo = temperature_servo
self.oled = oled
self.client = mqtt.Client()
self.client.on_connect = MessageTopic.on_connect
self.client.on_message = MessageTopic.on_message
self.client.connect(host="iot.eclipse.org",
port=1883,
keepalive=60)
MessageTopic.active_instance = self
def loop(self):
self.client.loop()
@staticmethod
def on_connect(client, userdata, flags, rc):
print("Connected to the {0} topic".
format(MessageTopic.topic))
subscribe_result = client.subscribe(MessageTopic.topic)
publish_result_1 = client.publish(
topic=MessageTopic.topic,
payload="Listening to messages in the Intel Galileo Gen 2 board")
@staticmethod
def on_message(client, userdata, msg):
if msg.topic == MessageTopic.topic:
print("I've received the following message: {0}".format(str(msg.payload)))
try:
message_dictionary = json.loads(msg.payload)
if MessageTopic.command_key in message_dictionary:
if message_dictionary[MessageTopic.command_key] == "print_temperature_fahrenheit":
MessageTopic.active_instance.temperature_servo.print_temperature(
message_dictionary["temperature_fahrenheit"])
MessageTopic.active_instance.publish_response_message(
message_dictionary)
elif message_dictionary[MessageTopic.command_key] == "print_information_message":
MessageTopic.active_instance.oled.print_line(
11, message_dictionary["text"])
MessageTopic.active_instance.publish_response_message(message_dictionary)
except ValueError:
# msg is not a dictionary
# No JSON object could be decoded
pass
def publish_response_message(self, message):
response_message = json.dumps({
self.__class__.successfully_processed_command_key:
message[self.__class__.command_key]})
result = self.client.publish(topic=self.__class__.topic,
payload=response_message)
return result
MessageTopic类声明了command_key类属性,该属性定义了键字符串,该字符串定义了代码将理解为什么命令。每当收到包含指定键字符串的消息时,我们知道与该键关联的字典中的值将指示消息希望代码在板子上处理的命令。在这种情况下,我们不会以字典的形式接收消息,因此当它们不是字符串时,有必要将它们从字符串转换为字典。
代码声明了successfully_processed_command_key类属性,该属性定义了键字符串,该字符串定义了代码将在发布到主题的消息中使用什么作为成功处理的命令键。每当发布包含指定键字符串的消息时,我们知道与该键关联的字典中的值将指示板已成功处理的命令。
我们必须在temperature_servo和oled必需参数中指定TemperatureServo实例和Oled实例。构造函数,即__init__方法,将接收到的参数保存到具有相同名称的两个属性中。topic类属性参数指定了我们将订阅以监听其他设备发送到该主题的消息的 Mosquitto 主题。我们还将向该主题发布消息,因此我们将成为该通道的订阅者和发布者。
然后,构造函数创建了一个mqtt.Client类的实例,该实例代表一个 MQTT 客户端,我们将用它与 MQTT 代理进行通信。由于我们使用默认参数创建实例,我们将创建一个paho.mqtt.client.MQTTv31实例,我们将使用 MQTT 版本 3.1。
代码还保存了这个实例在active_instance类属性中的引用,因为我们必须在静态方法中访问该实例,我们将指定为不同事件触发的 MQTT 客户端的回调。
然后,代码将self.client.on_connect属性分配给on_connect静态方法,将self.client.on_message属性分配给on_message静态方法。静态方法不接收self或cls作为第一个参数,因此我们可以使用它们作为具有所需参数数量的回调。
最后,构造函数调用self.client.connect方法,并在参数中指定了公开可访问的 Eclipse IoT 项目在 iot.eclipse.org 的沙盒服务器,端口 1883。这样,代码要求 MQTT 客户端建立与指定 MQTT 代理的连接。如果您决定使用自己的 Mosquitto 代理,只需根据 Mosquitto 代理的配置更改host和port参数的值。connect方法以异步执行方式运行,因此它是一个非阻塞调用。
在成功连接到 MQTT 代理后,self.client.on_connect 属性中指定的回调函数将被执行,即标记为 @staticmethod 装饰器的 on_connect 静态方法。这个静态方法接收 client 参数中的 mqtt.Client 实例,该实例与 MQTT 代理建立了连接。代码使用 MessageTopic.topic 作为参数调用 client.subscribe 方法,以订阅由 topic 类属性指定的主题。
小贴士
在这种情况下,我们只会订阅一个主题。然而,了解我们不仅限于订阅单个主题,我们可能通过一次调用 subscribe 方法订阅多个主题,这一点非常重要。
最后,代码使用 MessageTopic.topic 作为 topic 参数,以及一个消息字符串作为 payload 参数调用 client.publish 方法。这样,我们将一个字符串消息 "Listening to messages in the Intel Galileo Gen 2 board" 发布到由 topic 类属性指定的主题。
每当我们订阅的主题中收到新消息时,self.client.on_message 属性中指定的回调函数将被执行,即标记为 @staticmethod 装饰器的 on_message 静态方法。这个静态方法接收 client 参数中的 mqtt.Client 实例,该实例与 MQTT 代理建立了连接,以及 msg 参数中的 mqtt.MQTTMessage 实例。mqtt.MQTTMessage 类描述了一个传入的消息。首先,静态方法检查 msg.topic 属性,该属性指示接收消息的主题,是否与 topic 类属性中的值匹配。在这种情况下,每当 on_message 方法执行时,msg.topic 中的值将始终与 topic 类属性中的值匹配,因为我们只订阅了一个主题。然而,如果我们订阅了多个主题,则始终有必要检查消息发送的主题以及我们接收消息的主题。
代码打印接收到的消息,即 msg.payload 属性。然后,代码将 json.loads 函数的结果反序列化 msg.payload 到一个 Python 对象,并将结果赋值给 message_dictionary 本地变量。如果 msg.payload 的内容不是 JSON,将捕获 ValueError 异常,并且方法中不再执行更多代码。如果 msg.payload 的内容是 JSON,我们将在 message_dictionary 本地变量中有一个字典。
然后,代码检查 command_key 类属性是否包含在 message_dictionary 字典中。如果表达式评估为 True,则表示将 JSON 消息转换为字典时包含了一个我们必须处理的命令。然而,在我们能够处理命令之前,我们必须检查是哪个命令,因此有必要检索与 command_key 类属性等效的键关联的值。代码能够在值是我们在之前示例中与 PubNub 云一起工作时使用的两个命令中的任何一个时运行特定的代码。
代码使用了具有对活动 MessageTopic 实例引用的 active_instance 类属性来根据要处理的命令调用 temperature_servo 或 oled 属性所需的必要方法。我们不得不将回调声明为静态方法,因此我们使用这个类属性来访问活动实例。
一旦命令被成功处理,代码就会调用存储在 active_instance 类属性中的 MessageTopic 实例的 publish_response_message 方法。该方法接收包含在 message 参数中的带有命令接收到的消息字典。该方法调用 json.dumps 函数将字典序列化为一个带有响应消息的 JSON 格式化字符串,该消息指示命令已成功处理。最后,代码使用 topic 类属性作为 topic 参数和 JSON 格式化字符串(response_message)在 payload 参数中调用 client.publish 方法。
小贴士
在这种情况下,我们不是评估 publish 方法的响应。此外,我们正在使用 qos 参数的默认值,该参数指定了所需的服务质量。在更高级的场景中,我们应该添加代码来检查方法的结果,并可能在成功发布消息时触发的 on_publish 回调中添加代码。
现在,我们将使用之前编写的 MessageTopic 类来创建一个新的 __main__ 方法版本,该方法使用 Mosquitto 代理和 MQTT 客户端接收和处理命令。新版本在环境温度变化时不会旋转伺服电机的轴,相反,它将在接收到连接到 Mosquitto 代理的任何设备的适当命令时执行此操作。以下行显示了 __main__ 方法的新版本。示例代码文件为 iot_python_chapter_09_05.py。
if __name__ == "__main__":
temperature_and_humidity_sensor = \
TemperatureAndHumiditySensor(0)
oled = TemperatureAndHumidityOled(0)
temperature_servo = TemperatureServo(3)
message_topic = MessageTopic(temperature_servo, oled)
while True:
temperature_and_humidity_sensor.\
measure_temperature_and_humidity()
oled.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit,
temperature_and_humidity_sensor.temperature_celsius)
oled.print_humidity(
temperature_and_humidity_sensor.humidity)
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor.temperature_fahrenheit))
print("Ambient humidity: {0}".
format(temperature_and_humidity_sensor.humidity))
# Sleep 10 seconds (10000 milliseconds) but process messages every 1 second
for i in range(0, 10):
message_channel.loop()
time.sleep(1)
突出的行创建了一个之前编写的MessageTopic类的实例,其中temperature_servo和oled作为参数。构造函数将订阅 Mosquitto 代理中的"iot-python-gaston-hillar/temperature"主题,因此,我们必须向这个主题发布消息,以便发送代码将处理的命令。循环将读取传感器的值并将值打印到控制台,就像代码的先前的版本一样,因此,我们将在循环中运行代码,我们也将有代码在 Mosquitto 代理的"iot-python-gaston-hillar/temperature"主题上监听消息。循环的最后几行调用message_channel.loop方法 10 次,每次调用之间暂停 1 秒。loop方法调用 MQTT 客户端的循环方法,并确保与代理的通信得到执行。将调用loop方法视为同步您的邮箱。任何待发送的消息将发送到发件箱,任何传入的消息将到达收件箱,我们之前分析的事件将被触发。
小贴士
此外,我们还可以通过调用 MQTT 客户端的loop_start方法来运行一个线程接口。这样,我们可以避免多次调用loop方法。
以下行将启动示例。
python iot_python_chapter_09_05.py
在板上保持代码运行。稍后我们将开始接收消息,因为我们必须编写将消息发布到这个主题并发送要处理的命令的代码。
使用 Python 客户端向 Mosquitto 代理发布消息
我们有在 Intel Galileo Gen 2 板上运行的代码,用于处理从 Mosquitto 消息代理接收到的命令消息。现在,我们将编写一个 Python 客户端,该客户端将向"iot-python-gaston-hillar/temperature"通道发布消息。这样,我们将能够设计能够通过 MQTT 消息与物联网设备通信的应用程序。具体来说,应用程序将通过 Mosquitto 消息代理与发布器和订阅设备中的 Python 代码进行通信。
我们可以在另一个 Intel Galileo Gen 2 板上或在安装了 Python 2.7.x 的任何设备上运行 Python 客户端。此外,代码将以 Python 3.x 运行。例如,我们可以在我们的计算机上运行 Python 客户端。我们只需确保在板上的 Yocto Linux 中运行的 Python 版本中安装了我们之前用 pip 安装的pubnub模块。
我们将创建许多函数,并将它们作为回调函数分配给 MQTT 客户端的各个事件。此外,我们还将声明变量和一个辅助函数,以便能够轻松地使用命令和所需的值发布消息。示例的代码文件是 iot_python_chapter_09_06.py。别忘了将分配给 topic 变量的字符串替换为你之前代码中指定的主题名称。以下行显示了定义变量和函数的代码:
command_key = "command"
topic = "iot-python-gaston-hillar/temperature"
def on_connect(client, userdata, flags, rc):
print("Connected to the {0} topic".
format(topic))
subscribe_result = client.subscribe(topic)
publish_result_1 = client.publish(
topic=topic,
payload="Listening to messages in the Paho Python Client")
publish_result_2 = publish_command(
client,
topic,
"print_temperature_fahrenheit",
"temperature_fahrenheit",
45)
publish_result_3 = publish_command(
client,
topic,
"print_information_message",
"text",
"Python IoT")
def on_message(client, userdata, msg):
if msg.topic == topic:
print("I've received the following message: {0}".format(str(msg.payload)))
def publish_command(client, topic, command_name, key, value):
command_message = json.dumps({
command_key: command_name,
key: value})
result = client.publish(topic=topic,
payload=command_message)
return result
代码声明了 command_key 变量,它定义了指示代码在消息中理解为何种命令的关键字符串。我们的主要目标是构建并发布指定在 topic 变量中的主题的命令消息。我们将同时作为该主题的订阅者和发布者。
on_connect 函数是在与 Mosquitto MQTT 代理成功建立连接后执行的回调函数。代码调用 client 参数中接收到的 MQTT 客户端的 subscribe 方法,然后调用 publish 方法向主题发送以下字符串消息:"Listening to messages in the Paho Python Client"
代码使用必要的参数调用 publish_command 函数,构建并发布具有 45 度温度值的 print_temperature_fahrenheit 命令。最后,代码再次调用 publish_command 函数,使用必要的参数构建并发布具有文本值 "Python IoT" 的 print_information_message 命令。
publish_command 函数接收 MQTT 客户端、主题、命令名称、键和值,这些参数提供了在 client、topic、command_name、key 和 value 中执行命令所必需的信息。在这种情况下,我们不针对特定 IoT 设备的命令,而是所有订阅该主题并运行我们之前示例中的代码的设备都将处理我们发布的命令。我们可以将此代码作为基准,用于处理更复杂的示例,在这些示例中,我们必须生成针对特定 IoT 设备的命令。正如我们之前的示例中发生的那样,提高安全性也是必要的。
该函数创建一个字典,并将将字典序列化为 JSON 格式字符串的结果保存到 command_message 本地变量中。command_key 变量是字典的第一个键,command_name 作为参数接收,它构成了第一个键值对。然后,代码调用 client.publish 方法将 command_message JSON 格式字符串发布到作为参数接收的主题。
on_message 函数将在每次有新消息到达我们订阅的主题时执行。该函数只是打印带有接收消息有效载荷的原始字符串。
现在,我们将使用之前编写的functions来编写一个__main__方法,该方法发布 MQTT 消息中包含的两个命令,我们的板子将处理这些命令。以下行显示了__main__方法的代码。示例的代码文件是iot_python_chapter_09_06.py。
if __name__ == "__main__":
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect(host="iot.eclipse.org",
port=1883,
keepalive=60)
client.loop_forever()
__main__方法中的代码非常容易理解。代码创建了一个mqtt.Client类的实例,代表一个 MQTT 客户端,我们将使用它来与 MQTT 代理通信。由于我们使用默认参数创建实例,我们将创建一个paho.mqtt.client.MQTTv31的实例,我们将使用 MQTT 版本 3.1。
然后,代码将client.on_connect属性分配给之前编写的on_connect函数,将client.on_message属性分配给on_message函数。代码调用client.connect方法,并在参数中指定了 Eclipse IoT 项目在 iot.eclipse.org 上的公开可访问的沙盒服务器,端口为 1883。这样,代码请求 MQTT 客户端与指定的 MQTT 代理建立连接。如果你决定使用自己的 Mosquitto 代理,你只需根据 Mosquitto 代理的配置更改host和port参数的值。请记住,connect方法以异步执行方式运行,因此它是一个非阻塞调用。
在与 MQTT 代理成功建立连接后,client.on_connect属性中指定的回调函数将被执行,即on_connect函数。该函数接收在client参数中与 MQTT 代理建立连接的mqtt.Client实例。正如之前解释的那样,该函数订阅了一个主题,并安排向其发布三条消息。
最后,代码调用client.loop_forever方法,该方法以无限阻塞循环的方式为我们调用循环方法。在此阶段,我们只想在我们的程序中运行 MQTT 客户端循环。计划的消息将被发布,并且在我们将命令发送到板子后,我们将收到成功执行命令详情的消息。
保持我们在之前的示例中在板上运行的 Python 代码。我们希望板子处理我们的命令。以下行将在任何你想要用作客户端的计算机或设备上启动 Python 客户端的示例。如果你想在同一个板上使用相同的板子作为客户端,可以在另一个 SSH 终端中运行代码。
python iot_python_chapter_09_06.py
运行示例后,你将在运行 Python 客户端的 Python 控制台中看到以下输出,即iot_python_chapter_09_06.py Python 脚本。
Connected to the iot-python-gaston-hillar/temperature topic
I've received the following message: Listening to messages in the Paho Python Client
I've received the following message: {"command": "print_temperature_fahrenheit", "temperature_fahrenheit": 45}
I've received the following message: {"text": "Python IoT", "command": "print_information_message"}
I've received the following message: {"successfully_processed_command": "print_temperature_fahrenheit"}
I've received the following message: {"successfully_processed_command": "print_information_message"}
代码使用了 Eclipse Paho MQTT Python 客户端库,在 Mosquitto 代理的"iot-python-gaston-hillar/temperature"主题中构建并发布了以下两条命令消息:
{"command":"print_temperature_fahrenheit", "temperature_fahrenheit": "45"}
{"command":"print_information_message", "text": "Python IoT"}
由于我们也订阅了 "iot-python-gaston-hillar/temperature" 主题,我们收到了我们发送的消息。然后,我们收到了两个命令消息的成功处理命令消息。板已处理命令并将消息发布到 "iot-python-gaston-hillar/temperature" 主题。
你将在运行处理命令的板(即 iot_python_chapter_09_05.py Python 脚本)的 SSH 终端输出中看到以下消息:
I've received the following message: Listening to messages in the Intel Galileo Gen 2 board
I've received the following message: Listening to messages in the Paho Python Client
I've received the following message: {"command": "print_temperature_fahrenheit", "temperature_fahrenheit": 45}
I've received the following message: {"text": "Python IoT", "command": "print_information_message"}
I've received the following message: {"successfully_processed_command": "print_temperature_fahrenheit"}
I've received the following message: {"successfully_processed_command": "print_information_message"}
你将在 OLED 矩阵的底部看到以下文本显示:Python IoT。此外,伺服电机的轴将旋转到 45 度。
测试你的知识
-
MQTT 是:
-
在 TCP/IP 协议之上运行的重量级消息协议,并使用发布/订阅机制。
-
在 TCP/IP 协议之上运行的轻量级消息协议,并使用发布/订阅机制。
-
HTTP 的等价物。
-
-
Mosquitto 是:
-
实现 MQTT 协议版本 3.1 和 3.1.1 的开源消息代理。
-
实现 MQTT 协议版本 3.1 和 3.1.1 的闭源消息代理。
-
实现 RESTful API 的开源消息代理。
-
-
Eclipse Paho 项目提供:
-
HTTP 的开源客户端实现。
-
dweet.io的开源客户端实现。 -
MQTT 的开源客户端实现。
-
-
以下哪个 Python 模块是 Paho Python 客户端?
-
paho-client-pip。
-
paho-mqtt。
-
paho-http。
-
-
Dweepy 是:
-
一个简单的
dweet.ioPython 客户端,允许我们使用 Python 轻松发布数据到dweet.io。 -
一个简单的 Mosquitto Python 客户端,允许我们轻松地将消息发布到 Mosquitto 消息代理。
-
一个简单的 PubNub 云 Python 客户端,允许我们轻松地将消息发布到 PubNub 云。
-
摘要
在本章中,我们结合了许多基于云的服务,使我们能够轻松发布从传感器收集的数据,并在基于网页的仪表板上可视化它。我们意识到总有一个 Python API,因此,编写与流行的基于云服务交互的 Python 代码很容易。
我们与 MQTT 协议及其发布/订阅模型合作,处理板上的命令,并通过消息指示命令是否成功处理。首先,我们与使用 MQTT 协议的 PubNub 云合作。然后,我们用 Mosquitto 和 Eclipse Paho 开发了相同的示例。现在,我们知道如何编写可以与我们的物联网设备建立双向通信的应用程序。此外,我们还知道如何使物联网设备与其他物联网设备通信。
既然我们能够利用许多云服务,并且我们已经与 MQTT 协议合作,我们将学习如何分析大量数据,这是下一章的主题。
第十章. 使用基于云的物联网分析分析大量数据
在本章中,我们将使用英特尔物联网分析,利用这个强大的基于云的服务来分析大量数据。我们将:
-
理解物联网与大数据之间的关系
-
学习英特尔物联网分析的结构
-
在英特尔物联网分析中设置设备
-
在英特尔物联网分析中配置组件
-
使用英特尔物联网分析收集传感器数据
-
使用英特尔物联网分析分析传感器数据
-
在英特尔物联网分析中使用规则触发警报
理解物联网与大数据之间的关系
大数据在监视我们。每次我们执行一个动作,即使我们不知道,我们都在生成有价值的数据。每次我们轻触、点击、发推文、在红灯信号前停下、乘坐公交车,或者在世界任何城市的数百万个实时传感器捕捉到的任何动作,我们都在生成有价值的数据。我们与具有传感器的物联网设备互动,收集数据并将其发布到云端。为了分析和处理大数据,管理者、架构师、开发人员和系统管理员需要许多在处理较小数据集的应用程序中并不必要的技能。
我们一直在使用示例,通过传感器从现实世界收集数据并将其发布到云端。我们还发布了包含传感器数据和必须由物联网设备上运行的代码处理的命令的消息。有时,我们每秒从传感器中检索数据。因此,很容易意识到我们正在生成大量数据,因此,学习与大数据相关的大量知识非常重要。物联网包含大数据。
假设我们编写了在英特尔 Galileo Gen 2 板上运行的 Python 代码,并且每秒执行以下操作:
-
从温度和湿度传感器中读取测量的环境温度
-
从温度和湿度传感器中读取测量的环境湿度水平
-
从测量值位于不同位置的十个土壤湿度传感器中读取测量的体积水含量
-
发布包含环境温度、环境湿度和十个体积水含量的消息
可能首先出现在我们脑海中的是我们需要连接到板上的传感器数量。让我们假设所有的传感器都是数字传感器,并且我们必须将它们连接到 I²C 总线上。只要所有传感器都有不同的 I²C 总线地址,我们就可以将数字温度和湿度传感器以及十个土壤湿度传感器连接到 I²C 总线上。我们只需要确保我们可以配置土壤湿度传感器的 I²C 总线地址,并且我们可以为这些传感器中的每一个分配不同的 I²C 地址。
Catnip Electronics 设计了一种数字土壤湿度传感器,它提供了一个 I²C 接口,其一个特性是它允许更改 I²C 地址。该传感器的默认 I²C 地址为 0x20(十六进制 20),但我们可以轻松地更改它。我们只需将每个传感器连接到 I²C 总线,将新地址写入寄存器,并在重置传感器后,新地址将生效。我们只需将 6 写入传感器的 I²C 地址即可重置传感器。我们可以对所有的传感器执行相同的程序,并为它们分配不同的 I²C 地址。您可以在以下网页上了解更多关于数字土壤湿度传感器的信息:www.tindie.com/products/miceuz/i2c-soil-moisture-sensor。
我们希望分析每小时、每天、每月、每季度和每年的数据。然而,我们确实需要每秒测量一次,而不是每天测量一次,因为分析数据每秒的变化非常重要。我们将收集以下数据:
-
每分钟对所有变量进行 60 次测量
-
每小时 3,600(60 * 60)次测量
-
每天进行 86,400(3,600 x 24)次测量
-
每年 31,536,000(86,400 * 365)次测量(考虑到我们不是在谈论闰年)
我们不会只有一个物联网设备收集数据并发布它。我们将有 3,000 个物联网设备运行相同的代码,并且它们每年将生成 94,608,000,000(31,356,300 * 3,000),即九百四十亿六千八百万次测量。此外,我们还有其他必须分析的数据来源:所有关于传感器捕获数据位置天气相关问题的推文。因此,我们拥有大量结构化和非结构化数据,我们希望对其进行计算分析,以揭示模式和关联。我们无疑是在谈论大数据实践。
样本数量有助于理解大数据和物联网之间的关系。在下一个示例中,我们不会部署 3,000 块板,也不会涵盖与物联网分析和大数据相关的所有主题,因为这超出了本书的范围。然而,我们将使用基于云的分析系统,该系统与我们在第二章中使用的英特尔物联网开发套件镜像中的组件一起工作,以使用 Yocto Linux 元分布启动板。
理解英特尔物联网分析结构
假设我们需要收集和分析 3,000 个物联网设备的数据,即 3,000 个运行与传感器交互的 Python 代码的 Intel Galileo Gen 2 板。我们需要投资存储和处理能力来处理如此大量的物联网分析数据。每当我们有类似的需求时,我们可以利用基于云的解决方案。Intel IoT Analytics 就是其中之一,它与 Intel Galileo Gen 2 板和 Python 非常兼容。
Intel IoT Analytics 要求我们注册,使用有效的电子邮件地址和密码创建账户,并在发布传感器数据之前点击确认电子邮件的激活链接。我们不需要输入任何信用卡或支付信息。如果您已经在 Intel IoT Analytics 上有账户,您可以跳过此步骤。您也可以使用现有的 Facebook、Google+ 或 GitHub 账户登录。以下是 Intel IoT Analytics 网站的主页:dashboard.us.enableiot.com。在使用此基于云的服务处理敏感数据之前,请务必查看条款和条件。
一旦您在 Intel IoT Analytics 上创建账户并首次登录,网站将显示创建新账户页面。输入您希望用于识别账户的名称,即您的分析项目在账户名称中。以我们的示例为例,输入温度和湿度,并保留传感器健康报告的默认选项。然后,点击创建,网站将显示为最近创建的账户的我的仪表板页面。每个账户代表一个独立的工作空间,拥有自己的传感器和相关数据集。网站允许我们创建多个账户,并轻松地在它们之间切换。以下截图显示了创建新账户后我的仪表板页面的初始视图:

我的仪表板页面表明我们尚未注册任何设备,因此我们也没有传输设备或观测数据。每次我们从注册设备向 Intel IoT Analytics 发布数据时,我们都会为该设备创建一个观测数据。因此,我的仪表板页面提供了特定时间段内最后观测数据的数量。默认情况下,页面显示所有注册设备过去一小时的观测数据总和。请保持您的网页浏览器打开,因为我们稍后将继续使用它。
作为用户,我们可以与许多账户一起工作。每个账户可以包含许多设备,有一个名称和一个称为accountId的标识符。每个设备都有一个称为deviceId的全局唯一标识符。因此,每个包含传感器的英特尔 Galileo Gen 2 板将为我们创建的账户成为一台设备。在我们的情况下,我们只需与一个英特尔 Galileo Gen 2 板一起工作。然而,请记住,我们的目标是展示我们如何与单个账户处理的 3,000 个物联网设备一起工作。
我们可以将每个设备视为一个端点,该端点包含一个或多个组件,这些组件可以在英特尔物联网分析中提供以下之一:
-
执行器:可以在设备上修改的设置。例如,旋转伺服轴的角度或打开 LED。
-
时间序列:从传感器捕获的一系列值,即观测值的集合。例如,使用温度和湿度传感器检索的环境温度值集合,以华氏度表示,包括时间戳。
在我们的情况下,我们需要一个设备来使用以下组件,这些组件将从连接到我们板上的数字温度和湿度传感器中检索值:
-
一个以华氏度(ºF)表示的环境温度观测的时间序列
-
一个以摄氏度(ºC)表示的环境温度观测的时间序列
-
一个以百分比表示的环境湿度水平观测的时间序列
首先,我们将结合使用英特尔物联网分析网站提供的 UI 和iotkit-admin实用程序来设置设备、激活它并注册之前列表中包含的三个组件。这样,我们将学会与英特尔物联网分析所需的结构一起工作。然后,我们将编写 Python 代码,使用 REST API 为属于我们最近创建的账户中已激活设备的定义组件创建观测值。
我们也可以通过编写 Python 代码来使用 REST API 执行之前解释的设置任务。如果我们必须与超过一打设备一起工作,我们不会想通过使用英特尔物联网分析网站提供的 UI 来执行设置任务,我们肯定会想编写自动化设置任务的代码。
在英特尔物联网分析中设置设备
我们用来启动英特尔 Galileo Gen 2 板的映像中预装了英特尔物联网分析的本地代理。除非我们对 Yocto Linux meta 分布进行了特定更改以禁用特定组件,否则代理将以守护进程的形式在设备上运行。代理包括iotkit-admin命令行实用程序,允许我们与英特尔物联网分析进行特定交互。我们将使用此命令行实用程序执行以下任务:
-
测试与英特尔物联网分析的正确通信
-
获取设备 ID
-
激活设备
-
为设备注册三个时间序列组件。
-
发送测试观测值
首先,我们将检查iotkit-admin命令行工具是否能够与英特尔物联网分析服务建立适当的通信。我们只需在 SSH 终端中运行以下命令:
iotkit-admin test
如果连接成功,我们将看到类似以下的一些行。最后一行提供了有关构建的信息,即版本。
2016-04-05T02:17:49.573Z - info: Trying to connect to host ...
2016-04-05T02:17:56.780Z - info: Connected to dashboard.us.enableiot.com
2016-04-05T02:17:56.799Z - info: Environment: prod
2016-04-05T02:17:56.807Z - info: Build: 0.14.5
现在,在 SSH 终端中运行以下命令以获取设备 ID,也称为deviceId:
iotkit-admin device-id
上一条命令将生成类似以下行的输出行,其中包含设备 ID。默认情况下,设备 ID 等于网络接口卡的 MAC 地址。
2016-04-05T02:23:23.170Z - info: Device ID: 98-4F-EE-01-75-72
您可以使用以下命令将设备 ID 更改为不同的一个:iotkit-admin set-device-id new-device-id。您只需将new-device-id替换为您为设备设置的新的设备 ID。然而,请注意,新的设备 ID 必须是一个全局唯一标识符。
在这种情况下,我们将使用kansas-temperature-humidity-01作为我们所有示例的设备 ID。您必须在所有命令中替换它,然后包括您检索到的设备名称或分配给设备的新的设备 ID。
在 SSH 终端中运行的以下命令将重命名设备:
iotkit-admin set-device-id kansas-temperature-humidity-01
以下行显示了上一条命令的输出:
2016-04-08T17:56:15.355Z - info: Device ID set to: kansas-temperature-humidity
前往您正在使用的英特尔物联网分析仪表板的网页浏览器,点击菜单图标(位于左上角的一个有三个横线的按钮)。选择账户,网站将显示我们之前创建的账户的我的账户页面,其中包含详细的账户信息。
初始视图将显示详细信息选项卡。如果激活码包含(代码已过期)文本,则意味着激活码已不再有效,并且有必要点击位于激活码文本框右侧的刷新图标(第二个带有两个箭头的图标)。我们必须确保激活码尚未过期,以便成功激活设备。以下截图显示了我的账户页面的初始视图,对于温度和湿度账户,激活码已过期:

一旦您通过点击刷新按钮刷新激活码,倒计时计时器将指示激活码到期前的剩余时间。您在点击刷新按钮后将有一个小时。点击眼睛图标以查看隐藏的激活码并复制它。我们将使用01aCti0e作为我们的示例激活码,您必须将其替换为您自己的激活码。
现在,在 SSH 终端中运行以下命令以使用之前生成的激活码激活设备。将01aCti0e替换为您的激活码。
iotkit-admin activate 01aCti0e
上一条命令将生成类似以下行的输出:
2016-04-05T02:24:46.449Z - info: Activating ...
2016-04-05T02:24:49.817Z - info: Saving device token...
2016-04-05T02:24:50.646Z - info: Updating metadata...
2016-04-05T02:24:50.691Z - info: Metadata updated.
我们现在将英特尔 Galileo Gen 2 板,即设备,与提供激活代码并生成必要安全凭证(即设备令牌)的温度和湿度账户关联起来。
打开您正在使用英特尔物联网分析仪表板的浏览器,点击菜单图标(位于左上角的一个有三个水平线的按钮)。选择设备,网站将显示包含我们为当前账户激活的所有设备的我的设备页面。之前激活的 kansas-temperature-humidity-01 设备将出现在列表中,其名称列为Kansas-temperature-humidity-01-NAME,状态列为active。以下截图显示了我的设备页面中的设备列表:

点击前一个列表中的设备ID(kansas-temperature-humidity-01)以查看和编辑设备详细信息。您可以添加标签和属性,以便更容易过滤前一个列表中的设备。当我们需要处理超过一打设备时,这些功能非常有用,因为它们使我们能够轻松过滤列表中的设备。
在英特尔物联网分析中设置组件
打开您正在使用英特尔物联网分析仪表板的浏览器,点击菜单图标,选择账户,网站将显示我的账户页面。然后,点击目录选项卡,网站将显示按以下三个类别分组的目录中注册的组件:
-
湿度
-
Powerswitch
-
温度
确保已展开湿度组件面板,然后点击humidity.v1.0。网站将显示humidity.v1.0组件的组件定义对话框,即版本为 1.0 的名为humidity的组件。以下截图显示了组件定义中不同字段的值:

版本为 1.0 的 humidity 组件表示一个以百分比表示的环境湿度水平的时间序列。数据类型是数字,计量单位是百分比 (%),格式是浮点数,显示为时间序列。我们可以使用此组件来观察我们的环境湿度水平。
点击关闭,确保温度组件面板已展开,然后点击temperature.v1.0。网站将显示temperature.v1.0组件的组件定义对话框,即版本为 1.0 的名为temperature的组件。以下截图显示了组件定义中不同字段的值:

temperature组件版本1.0代表一个以摄氏度表示的温度的时间序列。数据类型是数字,测量单位是摄氏度,格式是浮点数,显示是时间序列。我们可以使用此组件来表示以摄氏度表示的环境温度观测。
点击关闭并确保温度组件面板已展开。没有其他温度组件,因此,我们必须为以华氏度表示的环境温度观测创建一个新的组件。
在页面底部点击添加新目录项,网站将显示包含所有字段为空的组件定义对话框,除了版本号将具有固定的1.0值。我们正在创建新目录项的第一个版本。输入并选择以下值:
-
在组件名称中输入temperaturef
-
在类型中选择传感器
-
在数据类型中选择数量
-
在测量单位中输入华氏度
-
在格式中选择浮点数
-
在显示中选择时间序列
最后,点击保存,网站将在列表底部添加名为temperaturef.v.1.0的新组件定义。
现在我们确信目录中包含所有必需的组件定义,我们必须注册设备将用于创建观测的组件。我们必须为每个注册的组件提供一个名称或别名,并必须指定来自上一目录的组件类型和版本。以下表格总结了我们将为设备注册的组件:
| 组件名称或别名 | 组件类型 | 描述 |
|---|---|---|
temperaturec |
temperature.v1.0 | 一个以摄氏度(ºC)表示的环境温度观测的时间序列 |
temperaturef |
temperaturef.v1.0 | 一个以华氏度(ºF)表示的环境温度观测的时间序列 |
humidity |
humidity.v1.0 | 一个以百分比表示的环境湿度水平观测的时间序列 |
我们可以使用以下命令来注册每个组件:iotkit-admin register component-name component-type。我们只需要将component-name替换为将标识组件的名称,将component-type替换为标识组件类型(包括版本号)的名称。
在 SSH 终端中的以下命令将注册来自上一表的temperaturec组件:
iotkit-admin register temperaturec temperature.v1.0
以下行显示了上一条命令的输出。
2016-04-08T22:40:04.581Z - info: Starting registration ...
2016-04-08T22:40:04.711Z - info: Device has already been activated. Updating ...
2016-04-08T22:40:04.739Z - info: Updating metadata...
2016-04-08T22:40:04.920Z - info: Metadata updated.
Attributes sent
2016-04-08T22:40:10.167Z - info: Component registered name=temperaturec, type=temperature.v1.0, cid=c37cb57d-002c-4a66-866e-ce66bc3b2340, d_id=kansas-temperature-humidity-01
最后一行为我们提供了组件 ID,即cid=之后和下一个逗号(,)之前的值。在上一个输出中,组件 ID 是c37cb57d-002c-4a66-866e-ce66bc3b2340。我们必须保存每个组件 ID,因为我们稍后需要它来编写使用 REST API 创建观测的代码。
在 SSH 终端中,以下命令将注册来自上一张表的temperaturef组件:
iotkit-admin register temperaturef temperaturef.v1.0
以下几行显示了上一条命令的输出结果:
2016-04-08T22:40:20.510Z - info: Starting registration ...
2016-04-08T22:40:20.641Z - info: Device has already been activated. Updating ...
2016-04-08T22:40:20.669Z - info: Updating metadata...
2016-04-08T22:40:20.849Z - info: Metadata updated.
Attributes sent
2016-04-08T22:40:26.156Z - info: Component registered name=temperaturef, type=temperaturef.v1.0, cid=0f3b3aae-ce40-4fb4-a939-e7c705915f0c, d_id=kansas-temperature-humidity-01
与其他命令一样,最后一行为我们提供了组件 ID,即cid=之后和下一个逗号(,)之前的值。在上一个输出中,组件 ID 是0f3b3aae-ce40-4fb4-a939-e7c705915f0c。我们必须保存这个 ID,以便在代码中的后续使用。
在 SSH 终端中,以下命令将注册来自上一张表的humidity组件:
iotkit-admin register humidity humidity.v1.0
以下几行显示了上一条命令的输出结果,最后一行包括组件 ID。
2016-04-08T22:40:36.512Z - info: Starting registration ...
2016-04-08T22:40:36.643Z - info: Device has already been activated. Updating ...
2016-04-08T22:40:36.670Z - info: Updating metadata...
2016-04-08T22:40:36.849Z - info: Metadata updated.
Attributes sent
2016-04-08T22:40:43.003Z - info: Component registered name=humidity, type=humidity.v1.0, cid=71aba984-c485-4ced-bf19-c0f32649bcee, d_id=kansas-temperature-humidity-01
小贴士
组件 ID 将与之前输出中指示的值不同,您需要记录由之前命令生成的每个组件 ID。
访问您正在使用的 Intel IoT Analytics 仪表板的网页浏览器,点击菜单图标。选择设备,站点将显示我的设备页面。点击之前列表中的设备ID(kansas-temperature-humidity-01)以查看和编辑设备详细信息。点击+组件以展开为设备注册的组件,您将看到一个包含以下三个组件的列表:
-
temperaturec
-
temperaturef
-
humidity
以下截图显示了为所选设备注册的三个组件:

我们可以点击这三个组件中的任何一个,并检查注册组件的详细信息。如果我们丢失了组件 ID,我们可以通过点击组件并显示组件定义对话框来检索它,组件 ID 将显示在组件类型描述下方。以下截图显示了temperaturef组件的组件定义。组件 ID 0f3b3aae-ce40-4fb4-a939-e7c705915f0c出现在右侧的自定义组件标签下方。

不幸的是,我们没有方法检索在激活设备时生成的设备令牌,该设备具有站点中包含的功能。我们需要设备令牌来为注册的组件创建观测值。Intel IoT Analytics 代理将设备令牌与其他配置值一起保存在device.json文件中,其默认路径是/usr/lib/node_modules/iotkit-agent/data/device.json。正如文件名所暗示的,该文件包含 JSON 代码。我们只需在 SSH 终端中运行以下命令,即可显示上一个文件的文本内容,并允许我们检索设备令牌:
cat /usr/lib/node_modules/iotkit-agent/data/device.json
以下几行显示了上一条命令的输出结果,其中包括我们迄今为止为设备所做的所有配置。定义设备令牌值的行被突出显示。
{
"activation_retries": 10,
"activation_code": null,
"device_id": "kansas-temperature-humidity-01",
"device_name": false,
"device_loc": [
88.34,
64.22047,
0
],
"gateway_id": false,
"device_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJjOTNmMTJhMy05MWZlLTQ3MWYtODM4OS02OGM1NDYxNDIxMDUiLCJpc3MiOiJodHRwOi8vZW5hYmxlaW90LmNvbSIsInN1YiI6ImthbnNhcy10ZW1wZXJhdHVyZS1odW1pZGl0eS0wMSIsImV4cCI6IjIwMjYtMDQtMDZUMTk6MDA6MTkuNzA0WiJ9.PH5yQas2FiQvUSR9V2pa3n3kIYZvmSe_xXY7QkFjlXUVUcyy9Sk_eVF4AL6qpZlBC9vjtd0L-VMZiULC9YXxAVl9s5Cl8ZqpQs36E1ssv_1H9CBFXKiiPArplzaWXVzvIRBVVzwfQrGrMoD_l4DcHlH2zgn5UGxhZ3RMPUvqgeneG3P-hSbPScPQL1pW85VT2IHT3seWyW1c637I_MDpHbJJCbkytPVpJpwKBxrCiKlGhvsh5pl4eLUXYUPlQAzB9QzC_ohujG23b-ApfHZugYD7zJa-05u0lkt93EEnuCk39o5SmPmIiuBup-k_mLn_VMde5fUvbxDt_SMI0XY3_Q",
"account_id": "22612154-0f71-4f64-a68e-e116771115d5",
"sensor_list": [
{
"name": "temperaturec",
"type": "temperature.v1.0",
"cid": "c37cb57d-002c-4a66-866e-ce66bc3b2340",
"d_id": "kansas-temperature-humidity-01"
},
{
"name": "temperaturef",
"type": "temperaturef.v1.0",
"cid": "0f3b3aae-ce40-4fb4-a939-e7c705915f0c",
"d_id": "kansas-temperature-humidity-01"
},
{
"name": "humidity",
"type": "humidity.v1.0",
"cid": "71aba984-c485-4ced-bf19-c0f32649bcee",
"d_id": "kansas-temperature-humidity-01"
}
]
}
前几行还显示了我们所注册的每个组件的组件 ID。因此,我们只需在一个地方就可以找到所有必要的配置值,这些值将用于我们的代码中。在这种情况下,设备令牌如下,即"device_token"键的字符串值。然而,您将检索到的值将不同。
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJjOTNmMTJhMy05MWZlLTQ3MWYtODM4OS02OGM1NDYxNDIxMDUiLCJpc3MiOiJodHRwOi8vZW5hYmxlaW90LmNvbSIsInN1YiI6ImthbnNhcy10ZW1wZXJhdHVyZS1odW1pZGl0eS0wMSIsImV4cCI6IjIwMjYtMDQtMDZUMTk6MDA6MTkuNzA0WiJ9.PH5yQas2FiQvUSR9V2pa3n3kIYZvmSe_xXY7QkFjlXUVUcyy9Sk_eVF4AL6qpZlBC9vjtd0L-VMZiULC9YXxAVl9s5Cl8ZqpQs36E1ssv_1H9CBFXKiiPArplzaWXVzvIRBVVzwfQrGrMoD_l4DcHlH2zgn5UGxhZ3RMPUvqgeneG3P-hSbPScPQL1pW85VT2IHT3seWyW1c637I_MDpHbJJCbkytPVpJpwKBxrCiKlGhvsh5pl4eLUXYUPlQAzB9QzC_ohujG23b-ApfHZugYD7zJa-05u0lkt93EEnuCk39o5SmPmIiuBup-k_mLn_VMde5fUvbxDt_SMI0XY3_Q"
使用英特尔物联网分析收集传感器数据
我们将使用我们在第八章中编写的代码,显示信息和执行操作,当我们从传感器读取温度和湿度值时,我们在 OLED 矩阵中打印了这些值,并旋转了伺服轴以显示以华氏度表示的测量温度。该示例的代码文件为iot_python_chapter_08_03.py。我们将使用此代码作为基准,添加新功能,使我们能够为我们激活的设备注册的三个组件创建观测值。
在第二章,在英特尔 Galileo Gen 2 上使用 Python中,我们确保了pip安装程序在板上运行的 Yocto Linux 中可用,以便安装额外的 Python 2.7.3 包。现在,我们将使用pip安装程序确保安装了requests包。这个包是一个非常流行的 Python HTTP 库,它允许我们使用极其容易理解的语法轻松构建和发送 HTTP 请求。
如果您已经处理了上一章的示例,您将已经安装了此包。然而,如果您刚刚跳到本章,可能需要安装它。我们只需在 SSH 终端中运行以下命令即可安装该包。请注意,安装可能需要几分钟时间。
pip install requests
如果您看到以下输出,则表示requests包已经安装,您可以继续下一步。
Requirement already satisfied (use --upgrade to upgrade): requests in /usr/lib/python2.7/site-packages
我们将创建一个IntelIotAnalytics类来表示英特尔物联网分析接口,并使我们能够轻松地为三个组件发布观测值。然而,在我们编写类代码之前,我们必须确保我们可以替换与我们的账户、组件和设备相关的重要值的相关类属性的内容。您必须将以下类属性指定的字符串替换为适当的值:
-
account_name: 账户名称字段在我的账户页面中的值。在我们的例子中,我们为账户名称使用了"Temperature and humidity"。 -
account_id: 账户 ID字段在我的账户页面中的值。在我们的例子中,我们为账户 ID 使用了"22612154-0f71-4f64-a68e-e116771115d5"。我们还可以通过读取device.json文件中指定的"account_id"键的字符串值来检索账户 ID 值。 -
device_id: 在我们点击我的设备页面中显示的列表中的设备名称时,添加/编辑设备页面中ID字段的值。在我们的例子中,我们使用"kansas-temperature-humidity-01"作为我们的设备 ID。我们也可以通过在 SSH 终端中运行以下命令来检索device_id:iotkit-admin device-id,或者通过读取device.json文件中指定的"device_id"键的字符串值。 -
device_token: 当我们激活设备时生成的设备令牌的值。如前所述,我们可以通过读取device.json文件中指定的"device_token"键的字符串值来检索设备令牌。 -
component_id_temperature_fahrenheit: 当我们注册了temperaturef组件时生成的组件 ID 的值。组件 ID 显示在组件定义对话框中组件类型下方。在我们的例子中,我们使用"0f3b3aae-ce40-4fb4-a939-e7c705915f0c"作为这个值。我们也可以通过读取device.json文件中声明"name": "temperaturef"键值对同一块中指定的"cid"键的字符串值来检索组件 ID 值。 -
component_id_temperature_celsius: 当我们注册了temperaturec组件时生成的组件 ID 的值。在我们的例子中,我们使用"c37cb57d-002c-4a66-866e-ce66bc3b2340"作为这个值。 -
component_id_humidity_level_percentage: 当我们注册了humidity组件时生成的组件 ID 的值。在我们的例子中,我们使用"71aba984-c485-4ced-bf19-c0f32649bcee"作为这个值。
样本的代码文件是iot_python_chapter_10_01.py。请记住,我们使用代码文件iot_python_chapter_08_03.py作为基线,因此,我们将把IntelIotAnalytics类添加到该文件中的现有代码中,并创建一个新的 Python 文件。以下代码展示了允许我们通过 REST API 发布temperaturef、temperaturec和humidity组件观察结果的IntelIotAnalytics类。
import time
import json
import requests
class IntelIotAnalytics:
base_url = "https://dashboard.us.enableiot.com/v1/api"
# You can retrieve the following information from the My Account page
account_name = "Temperature and humidity"
account_id = "22612154-0f71-4f64-a68e-e116771115d5"
# You can retrieve the device token with the following command:
# cat /usr/lib/node_modules/iotkit-agent/data/device.json
device_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJjOTNmMTJhMy05MWZlLTQ3MWYtODM4OS02OGM1NDYxNDIxMDUiLCJpc3MiOiJodHRwOi8vZW5hYmxlaW90LmNvbSIsInN1YiI6ImthbnNhcy10ZW1wZXJhdHVyZS1odW1pZGl0eS0wMSIsImV4cCI6IjIwMjYtMDQtMDZUMTk6MDA6MTkuNzA0WiJ9.PH5yQas2FiQvUSR9V2pa3n3kIYZvmSe_xXY7QkFjlXUVUcyy9Sk_eVF4AL6qpZlBC9vjtd0L-VMZiULC9YXxAVl9s5Cl8ZqpQs36E1ssv_1H9CBFXKiiPArplzaWXVzvIRBVVzwfQrGrMoD_l4DcHlH2zgn5UGxhZ3RMPUvqgeneG3P-hSbPScPQL1pW85VT2IHT3seWyW1c637I_MDpHbJJCbkytPVpJpwKBxrCiKlGhvsh5pl4eLUXYUPlQAzB9QzC_ohujG23b-ApfHZugYD7zJa-05u0lkt93EEnuCk39o5SmPmIiuBup-k_mLn_VMde5fUvbxDt_SMI0XY3_Q"
device_id = "kansas-temperature-humidity-01"
component_id_temperature_fahrenheit = "0f3b3aae-ce40-4fb4-a939-e7c705915f0c"
component_id_temperature_celsius = "c37cb57d-002c-4a66-866e-ce66bc3b2340"
component_id_humidity_level_percentage = "71aba984-c485-4ced-bf19-c0f32649bcee"
def publish_observation(self,
temperature_fahrenheit,
temperature_celsius,
humidity_level):
url = "{0}/data/{1}".\
format(self.__class__.base_url, self.__class__.device_id)
now = int(time.time()) * 1000
body = {
"on": now,
"accountId": self.__class__.account_id,
"data": []
}
temperature_celsius_data = {
"componentId": self.__class__.component_id_temperature_celsius,
"on": now,
"value": str(temperature_celsius)
}
temperature_fahrenheit_data = {
"componentId": self.__class__.component_id_temperature_fahrenheit,
"on": now,
"value": str(temperature_fahrenheit)
}
humidity_level_percentage_data = {
"componentId": self.__class__.component_id_humidity_level_percentage,
"on": now,
"value": str(humidity_level)
}
body["data"].append(temperature_celsius_data)
body["data"].append(temperature_fahrenheit_data)
body["data"].append(humidity_level_percentage_data)
data = json.dumps(body)
headers = {
'Authorization': 'Bearer ' + self.__class__.device_token,
'content-type': 'application/json'
}
response = requests.post(url, data=data, headers=headers, proxies={}, verify=True)
if response.status_code != 201:
print "The request failed. Status code: {0}. Response text: {1}.".\
format(response.status_code, response.text)
IntelIotAnalytics类声明了许多我们之前解释过的类属性,您需要用您自己的字符串值替换它们:account_name、account_id、device_token、device_id、component_id_temperature_fahrenheit、component_id_temperature_celsius和component_id_humidity_level_percentage。base_url类属性定义了访问 REST API 的基本 URL:https://dashboard.us.enableiot.com/v1/api。我们将使用这个值与data路径和device_id类属性结合来构建我们将发送 HTTP 请求以发布观察结果的 URL。
类声明了publish_observation方法,该方法接收华氏度表示的温度、摄氏度表示的温度和湿度百分比,参数为temperature_fahrenheit、temperature_celsius和humidity_level。该方法构建我们将发送 HTTP 请求以创建设备及其三个组件观察值的 URL。URL 由base_url类属性、/data/和device_id类属性组成。正如许多 REST API 所发生的那样,base_url类属性指定了 API 的版本号。这样,我们确保我们始终在特定版本上工作,并且我们的请求与该版本兼容。代码将构建的 URL 的值保存到url局部变量中。
然后,代码将板当前时间(以秒为单位乘以 1000)保存到now局部变量中。代码创建一个body字典,该字典代表请求的正文,并包含以下键值对:
-
"on": 存储在now局部变量中的值,即板的当前时间。它是观察的时间。 -
"accountId": 存储在accountId类属性中的值,即我们将发布观察值的 Intel IoT Analytics 账户。 -
"data": 一个空数组,我们将在稍后用每个组件的一个观察值填充。
然后,代码创建三个字典,包含以下键值对,代表特定组件的观察值:
-
"componentId": 存储在指定要发布观察值的组件 ID 的类属性中的值。 -
"on": 存储在now局部变量中的值,即板的当前时间。它是观察的时间。我们使用相同的变量对所有观察值进行操作,因此它们以相同的时间注册。 -
"value": 方法中作为参数接收的值的字符串表示。
然后,代码调用append方法将三个字典添加到body字典中的data键。这样,data键将有一个包含三个字典的数组作为其值。代码调用json.dumps函数将body字典序列化为 JSON 格式的字符串,并将其保存到data局部变量中。
下一行创建一个headers字典,包含以下键值对,代表 HTTP 请求的头部:
-
"Authorization": 由"Bearer"和保存在device_token类属性中的设备令牌连接而成的授权字符串 -
"content-type": 声明内容类型为 JSON:"application/json"
在这个阶段,代码已经构建了用于发布观测数据到英特尔物联网分析服务的 HTTP 请求的头部和主体。下一行调用requests.post函数,向由url局部变量指定的 URL 发送一个 HTTP POST 请求,其中data字典作为 JSON 主体数据,headers字典作为头部。
requests.post方法返回一个保存在response局部变量的响应,代码评估响应的 code 属性是否不等于 201。如果 code 与 201 不同,这意味着观测数据没有成功发布,即出了问题。在这种情况下,代码将响应的status_code和text属性的值打印到控制台输出,以便我们了解出了什么问题。如果我们使用错误的设备令牌或账户、设备或组件的 id,我们将收到错误。
现在,我们将使用之前编写的IntelIoTAnalytics类来创建一个新的__main__方法版本,该方法每 5 秒向英特尔物联网分析服务发布一次观测数据。以下行显示了新的__main__方法。示例的代码文件为iot_python_chapter_10_01.py。
if __name__ == "__main__":
temperature_and_humidity_sensor = \
TemperatureAndHumiditySensor(0)
oled = TemperatureAndHumidityOled(0)
intel_iot_analytics = IntelIotAnalytics()
while True:
temperature_and_humidity_sensor.\
measure_temperature_and_humidity()
oled.print_temperature(
temperature_and_humidity_sensor.temperature_fahrenheit,
temperature_and_humidity_sensor.temperature_celsius)
oled.print_humidity(
temperature_and_humidity_sensor.humidity)
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor.temperature_fahrenheit))
print("Ambient humidity: {0}".
format(temperature_and_humidity_sensor.humidity))
intel_iot_analytics.publish_observation(
temperature_and_humidity_sensor.temperature_fahrenheit,
temperature_and_humidity_sensor.temperature_celsius,
temperature_and_humidity_sensor.humidity
)
# Sleep 5 seconds (5000 milliseconds)
time.sleep(5)
突出的行显示了创建之前创建的IntelIoTAnalytics类实例并将其引用保存到intel_iot_analytics局部变量的代码。然后,在每 5 秒运行一次的循环中的代码使用从温度和湿度传感器检索的温度和湿度值作为参数调用publish_observation方法。
以下行将启动示例:
python iot_python_chapter_10_01.py
运行示例后,打开空调或加热系统,以产生环境温度和湿度的变化。这样,我们将注意到每 5 秒发布的数据中的变化。在我们探索英特尔物联网分析服务中包含的不同功能时,请保持代码运行。
前往您正在使用的英特尔物联网分析仪表板的网页浏览器,点击菜单图标并选择仪表板。网站将显示我的仪表板页面,它将指示您有一个活动设备,并且当它从板子上接收观测数据时,它将更新过去一小时发布的观测数据数量。以下图片显示了包含活动设备和包含过去一小时发布945次观测数据的计数器的仪表板:

保持浏览器打开并显示仪表板视图,您会注意到在过去一小时中观察值增加,因为代码在板上继续运行。您可以在显示观察数量和上下文菜单的面板右上角点击配置图标,上下文菜单允许您配置在此面板中查看的观察周期。例如,您可以将上一小时更改为上一周以显示设备在过去一周内注册的观察数量。
使用英特尔物联网分析工具分析传感器数据
英特尔物联网分析工具允许我们为具有特定设备观察数据的每个组件生成图表。首先,我们必须选择设备,然后我们必须选择一个或多个组件以生成带有历史时间序列或由板上运行的代码生成的时间序列的图表,即组件的实时数据。
打开您正在使用英特尔物联网分析仪表板的浏览器,点击菜单图标并选择图表。网站将显示我的图表页面,允许您使用许多搜索条件搜索设备,例如设备名称、相关标签及其属性。
在这种情况下,我们只有一个激活的设备,因此我们可以从网站在选择设备部分下方显示的设备列表中选择设备。此部分在复选框的右侧显示设备名称的首个字符,并在文本的右侧显示已为此设备注册的组件数量。以下图片显示了选择设备部分,其中kansas-temp…代表kansas-temperature-humidity-01设备。如果您将鼠标悬停在复选框上或轻触文本,网站将显示一个弹出窗口,显示设备的完整名称和已注册组件的类型。以下截图显示了包含此信息的弹出窗口,显示在kansas-temp…复选框上。

选择kansas-temp…复选框,网站将显示所选设备的三个注册组件。在这种情况下,网站显示组件名称(temperaturec、temperaturef和humidity),而在之前解释的弹出窗口中,网站显示了组件类型(temperature.v1.0、temperaturef.v1.0和humidity.v1.0)。
选择temperaturef复选框,网站将显示过去一小时测量的环境温度图表,单位为华氏度。默认情况下,图表使用线条并生成过去一小时记录的时间序列值图表。默认情况下,图表的刷新率为 30 秒,因此图表将每 30 秒更新一次,并显示在此期间通过 REST API 由板发布的所有新观测值。

我们可以使用图表顶部的不同按钮来更改图表类型,并选择我们想要在图表中显示的时间范围。我们还可以将刷新率更改为低至 5 秒或高达 60 秒。如果我们将图表保存为收藏夹,网站将在我的仪表板中将其显示为仪表板的一部分。
点击位于图表按钮(一个带有山脉的图片图标)右侧的原始数据按钮(一个子弹图标)。网站将显示一个列表,其中包含用于构建时间序列的原始数据,即所选组件接收到的所有观测值。以下截图显示了过去一小时temperaturef组件的原始数据视图的第一页。

在这个例子中,生成一个包含温度和湿度水平的图表非常有用。通过点击图表按钮(一个带有山脉的图片图标)返回图表视图,并勾选湿度复选框。这样,网站将生成一个图表,结合华氏度表示的温度和百分比表示的湿度水平。以下截图显示了当temperaturef和humidity都勾选时生成的图表:

使用英特尔物联网分析中的规则触发警报
英特尔物联网分析允许我们定义可以触发以下任何通知类型的规则:
-
电子邮件
-
HTTP 端点
-
动作
打开你正在使用的英特尔物联网分析仪表板的网页浏览器,点击菜单图标并选择规则。网站将显示我的规则页面,允许你为激活的设备定义规则。在这种情况下,我们将定义一个规则,当湿度水平低于 10%时,将发送电子邮件给我们。
点击添加规则,网站将显示一个表单,允许我们输入新规则的详细信息。在规则名称中输入非常低的湿度水平,在优先级中选择低,在通知类型中选择电子邮件。在通知对象面板的下拉菜单中选择你想要接收通知的电子邮件地址。
点击下一步,网站将要求我们选择要应用新规则的设备。在这种情况下,我们只有一个激活的设备,因此我们可以从网站在选择设备部分下方显示的设备列表中选择设备。如前述设备选择页面所示,此部分在复选框的右侧显示设备名称的第一个字符,在文本的右侧显示已为此设备注册的组件数量。勾选kansas-temp…复选框,名称将出现在已选设备列表中。
点击下一步,网站将要求我们指定新规则的条件。不要勾选启用自动重置复选框,因为我们希望规则在每次警报后变为不活动状态,直到被确认。这样,在收到警报后,我们只有在确认了第一个生成的警报后才会收到额外的警报。
在监控度量中选择湿度(数字),在触发条件中选择基本条件。然后,在出现的附加下拉菜单中选择<,并在输入值文本框中输入10。这样,我们正在创建一个当湿度观测值低于 10(湿度 < 10)时将触发的规则。以下截图显示了定义的条件:

点击完成,规则将被添加到我的规则中显示的列表中。以下截图显示了定义后包含在此列表中的规则定义:

当湿度低于 10%时,将触发警报,我们将在警报图标(铃铛)中看到一个数字 1。点击图标后,网站将显示我们所有未读的警报。以下截图显示了包含一个未读警报的我的仪表盘页面:

如果我们点击警报,网站将显示触发警报的情况的详细信息。我们还可以在菜单中选择警报,查看接收到的警报列表。以下截图显示了包含在接收到的警报列表中的警报:

如果我们点击警报编号,网站将显示警报的详细信息,包括触发警报的规则中定义的条件和测量的值。在这种情况下,测量的值是7.99。可以向警报添加注释。以下截图显示了警报的详细信息:

此外,我们还会收到一封包含以下文本的电子邮件:
Alert Monitor has received an alert. Alert data:
- Alert Id: 1
- Device: kansas-temperature-humidity-01
- Reason: humidity < 10
- Priority: Low
Alert Data
Component Name Values
humidity 7.99;
You can go here to check it on Dashboard
Regards
在这种情况下,我们在规则中定义了一个非常简单的条件。然而,我们可以定义一个更复杂的条件,该条件可以包括以下任何条件:
-
基于时间的条件
-
基于统计的条件
-
单传感器变化检测
-
多感官变化检测
我们可以玩转不同的选项,为具有多个传感器和大量数据的众多设备触发警报。英特尔物联网分析最有趣的功能之一是我们可以用图表、规则和警报轻松地处理大量数据。
测试你的知识
-
英特尔物联网分析中每个设备的组件可以是:
-
执行器或时间序列。
-
账户、执行器或时间序列。
-
代理、账户、执行器或时间序列。
-
-
每次我们将从注册设备发布数据到英特尔物联网分析时,我们都会创建:
-
执行器。
-
账户。
-
观察结果。
-
-
时间序列是:
-
执行器执行的一系列动作,即动作的集合。
-
从传感器捕获的一系列值,即观察的集合。
-
一系列触发的警报,即警报的集合。
-
-
我们可以使用以下命令行工具激活我们的板作为英特尔物联网分析账户中的一个设备:
-
iotkit-admin
-
iotkit-configure
-
iotkit-setup
-
-
为了使用英特尔物联网分析提供的 REST API 从设备发送观察结果,我们需要以下令牌:
-
传感器令牌。
-
观察令牌。
-
设备令牌。
-
摘要
在本章中,我们了解了物联网与大数据之间的紧密关系。我们与一个基于云的服务合作,该服务允许我们组织由多个设备和其传感器收集的大量数据。我们利用requests包编写了几行 Python 代码,可以与英特尔物联网分析 REST API 交互。
我们使用英特尔物联网分析网站来设置设备和其组件。然后,我们对我们的一个示例进行了修改,以从传感器收集数据并将观察结果发布到英特尔物联网分析。然后,我们了解了英特尔物联网分析为我们提供的不同选项,以分析大量数据。最后,我们定义了触发警报的规则。现在,我们能够利用英特尔物联网分析来分析大量数据,我们准备部署成千上万的物联网设备,这些设备从多个传感器收集数据。
我们学会了使用 Python 和英特尔 Galileo Gen 2 板来创建低成本设备,这些设备可以收集大量数据,相互交互,并利用云服务和基于云的存储。我们可以使用 Python 2.7.3 及其库和工具从硬件选择到所有必要的堆栈开发物联网原型。如果我们需要更小的板或不同的替代品,我们可以切换到任何兼容的英特尔 Edison 板,因此,如果我们需要,我们可以切换到这块板。
我们能够利用现有的 Python 知识从现实世界中捕获数据,与物理对象交互,开发 API 并使用不同的物联网协议。我们学会了使用特定的库来处理底层硬件、传感器、执行器、总线和显示器。我们准备好成为创造者,并成为激动人心的物联网世界的一部分。
我们可以开始着手于将日常物体转变为带有传感器和执行器的智能设备的项目。我们准备好开始构建由数千个物联网设备组成的生态系统,Python 作为我们的主要编程语言。
附录 A. 练习答案
第一章,理解和设置基础物联网硬件
| Q1 | 2 |
|---|---|
| Q2 | 1 |
| Q3 | 2 |
| Q4 | 3 |
| Q5 | 1 |
第二章,在英特尔 Galileo Gen 2 上使用 Python
| Q1 | 2 |
|---|---|
| Q2 | 1 |
| Q3 | 2 |
| Q4 | 3 |
| Q5 | 1 |
第三章,使用 Python 与数字输出交互
| Q1 | 3 |
|---|---|
| Q2 | 1 |
| Q3 | 1 |
| Q4 | 2 |
| Q5 | 2 |
第四章,使用 RESTful API 和脉宽调制进行工作
| Q1 | 3 |
|---|---|
| Q2 | 3 |
| Q3 | 2 |
| Q4 | 1 |
| Q5 | 2 |
第五章,使用数字输入、轮询和中断
| Q1 | 1 |
|---|---|
| Q2 | 2 |
| Q3 | 1 |
| Q4 | 2 |
| Q5 | 3 |
第六章,使用模拟输入和本地存储
| Q1 | 3 |
|---|---|
| Q2 | 1 |
| Q3 | 2 |
| Q4 | 1 |
| Q5 | 3 |
第七章,使用传感器从现实世界获取数据
| Q1 | 2 |
|---|---|
| Q2 | 1 |
| Q3 | 2 |
| Q4 | 3 |
| Q5 | 1 |
第八章,显示信息和执行操作
| Q1 | 1 |
|---|---|
| Q2 | 1 |
| Q3 | 3 |
| Q4 | 3 |
| Q5 | 2 |
第九章,使用云服务
| Q1 | 2 |
|---|---|
| Q2 | 1 |
| Q3 | 3 |
| Q4 | 2 |
| Q5 | 1 |
第十章,使用基于云的物联网分析分析大量数据
| Q1 | 1 |
|---|---|
| Q2 | 3 |
| Q3 | 2 |
| Q4 | 1 |
| Q5 | 3 |




浙公网安备 33010602011771号