Python-Arduino-编程指南-全-
Python Arduino 编程指南(全)
原文:
zh.annas-archive.org/md5/a92daa7ed7e63d78de20d04520325dc2译者:飞龙
前言
在物联网(IoT)时代,快速开发和测试你的硬件产品的原型,同时使用软件特性对其进行增强,变得非常重要。Arduino 运动在这一硬件革命中一直处于领先地位,通过其简单的板设计,它使任何人都能方便地开发 DIY 硬件项目。开源社区提供的巨大支持使得与硬件原型开发相关的困难已成为过去。在软件方面,Python 已经很长时间一直是开源软件社区的瑰宝。Python 拥有大量的库来开发各种特性,如图形用户界面、图表、消息和云应用。
本书旨在将硬件和软件世界的最佳结合带给您,帮助您使用 Arduino 和 Python 开发令人兴奋的项目。本书的主要目标是帮助读者解决将 Arduino 硬件与 Python 库接口的难题。同时,作为次要目标,本书还提供了可以用于您未来物联网项目的练习和项目。
本书的设计方式是,每一章在涵盖的材料复杂性和实用性方面都有所增加。本书分为三个概念部分(入门、实现 Python 特性、网络连接),每个部分都以一个实际项目结束,该项目整合了你在该部分学到的概念。
本书涵盖的理论概念和练习旨在让您获得 Python-Arduino 编程的实践经验,而项目旨在教授您为未来项目设计的硬件原型设计方法。然而,您仍需要在每个领域拥有广泛的专长才能开发出商业产品。最后,我希望为您提供足够的知识,以帮助您在这个新颖的物联网领域开始您的旅程。
本书涵盖的内容
第一章, Python 和 Arduino 入门,介绍了 Arduino 和 Python 平台的基础知识。它还提供了全面的安装和配置步骤,以设置必要的软件工具。
第二章, 使用 Firmata 协议和 pySerial 库,通过解释 Firmata 协议和串行接口库来讨论 Arduino 硬件与 Python 程序的接口。
第三章,第一个项目 – 运动触发 LED,提供了创建你的第一个 Python-Arduino 项目的全面指南,该项目根据检测到的运动控制不同的 LED。
第四章,深入 Python-Arduino 原型设计,带你超越我们在前一个项目中执行的基本原型设计,并提供了关于原型设计方法的深入描述,附有适当的示例。
第五章,使用 Python GUI,开始了我们用 Python 开发图形界面的两章之旅。本章介绍了 Tkinter 库,它为 Arduino 硬件提供了图形前端。
第六章,存储和绘制 Arduino 数据,涵盖了用于存储和绘制传感器数据的 Python 库、CSV 和 matplotlib。
第七章,中期项目 – 可携式 DIY 恒温器,包含了一个实用且可部署的项目,该项目利用了我们之前章节中涵盖的材料,如串行接口、图形前端和传感器数据的绘图。
第八章,Arduino 网络介绍,在利用各种协议建立 Python 程序与 Arduino 之间的以太网通信的同时,介绍了 Arduino 的计算机网络。本章还探讨了名为 MQTT 的消息协议,并提供了基本示例。该协议专门为资源受限的硬件设备,如 Arduino,而设计。
第九章,Arduino 与物联网,讨论了物联网领域,同时提供了逐步指南来开发基于云的物联网应用程序。
第十章,最终项目 – 远程家庭监控系统,教授了硬件产品的设计方法,随后是一个将云平台与 Arduino 和 Python 接口的综合项目。
第十一章,Tweet-a-PowerStrip,包含了一个基于我们在书中所学内容的另一个物联网项目。该项目探索了一种独特的将社交网络、Twitter,与 Python-Arduino 应用程序集成的途径。
你需要这本书的
首先,您只需要一台装有支持操作系统的计算机,Windows、Mac OS X 或 Linux。本书需要各种额外的硬件组件和软件工具来实现编程练习和项目。每个章节中都包含所需的硬件组件列表和获取这些组件的位置。
在软件方面,本书提供了逐步指南,用于安装和配置本书中使用的所有必要的软件包和依赖库。请注意,本书中包含的练习和项目是为 Python 2.7 设计的,并且尚未针对 Python 3+进行测试。
本书面向对象
如果您是学生、爱好者、开发者或设计师,编程和硬件原型设计经验很少或没有,并且您想开发物联网应用,那么这本书就是为您准备的。
如果您是软件开发者,并希望获得硬件领域的经验,这本书将帮助您开始。如果您是希望学习高级软件功能的硬件工程师,这本书可以帮助您开始。
约定
在本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 标签应如下显示:“在将值赋给weight变量时,我们没有指定数据类型,但 Python 解释器将其指定为整型,int。”
代码块设置如下:
/*
Blink
Turns on an LED on for one second, then off for one second, repeatedly.
This example code is in the public domain.
*/
// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;
// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
}
// the loop routine runs over and over again forever:
void loop() {
digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
任何命令行输入或输出都应如下编写:
$ sudo easy_install pip
新术语和重要词汇以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中如下显示:“在系统窗口中,点击左侧导航栏中的高级系统设置以打开名为系统属性的窗口。”
注意
警告或重要注意事项如下所示。
小贴士
小技巧如下所示。
读者反馈
我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或不喜欢什么。读者反馈对我们来说很重要,因为它帮助我们开发出您真正能从中获得最大收益的书籍。
要向我们发送一般反馈,只需发送电子邮件至<feedback@packtpub.com>,并在邮件主题中提及书籍的标题。
如果您在某个主题上具有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在,您已经成为 Packt 书籍的骄傲拥有者,我们有一些可以帮助您充分利用购买的东西。
下载示例代码
您可以从您在 www.packtpub.com 的账户下载示例代码文件,适用于您购买的所有 Packt Publishing 书籍。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。
下载本书的颜色图像
我们还为您提供了一个包含本书中使用的截图/图表颜色图像的 PDF 文件。这些颜色图像将帮助您更好地理解输出的变化。您可以从以下链接下载此文件:www.packtpub.com/sites/default/files/downloads/5938OS_ColoredImages.pdf。
错误清单
尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然可能发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何错误清单,请通过访问 www.packtpub.com/submit-errata,选择您的书籍,点击错误清单提交表单链接,并输入您的错误清单详情。一旦您的错误清单得到验证,您的提交将被接受,错误清单将被上传到我们的网站或添加到该标题的错误清单部分。
要查看之前提交的错误清单,请访问 www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在错误清单部分。
盗版
互联网上版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现任何形式的我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过以下链接联系我们 <copyright@packtpub.com>,并提供涉嫌盗版材料的链接。
我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。
问题
如果您对本书的任何方面有问题,您可以通过以下链接联系我们 <questions@packtpub.com>,我们将尽力解决问题。
第一章. Python 和 Arduino 入门
本章介绍了 Python 编程语言和开源电子原型平台 Arduino。本章的第一部分专注于 Python,并简要描述了 Python 的优势以及安装和配置步骤。本章的其余部分描述了 Arduino 及其开发环境。
在本章结束时,你将为你的操作系统配置好用于 Python 和 Arduino 的编程环境。如果你是这两个平台(即 Python 和 Arduino)的初学者,建议你遵循本章中给出的步骤,因为后续章节将假设你已经拥有了这里描述的精确配置。如果你有在这些平台上工作的经验,你可以跳到下一章。
Python 简介
自 1991 年由 Guido van Rossum 引入以来,Python 已经发展成为最广泛使用的通用、高级编程语言之一,并得到了最大的开源开发者社区的支持。Python 是一种开源编程语言,包括大量的支持库。这些库是 Python 的最好特性,使其成为最可扩展的平台之一。Python 是一种动态编程语言,它使用解释器在运行时执行代码,而不是使用编译器编译和创建可执行的字节码。
Python 开发背后的哲学是创建灵活、可读和清晰的代码,以便轻松表达概念。强调以独特的方式使用空白缩进区分 Python 与其他流行的面向高级语言。Python 支持函数式、命令式和面向对象编程,具有自动内存管理功能。
我们为什么使用 Python
Python 被认为是最容易学习的编程语言之一,对于初学者来说。与其他流行的面向对象语言如 C++和 Java 相比,Python 对程序员有以下主要优势:
-
它易于阅读和理解
-
它能够实现快速原型设计并减少开发时间
-
它拥有大量的免费库包
Python 拥有一个庞大的开源社区,推动着 Python 作为编程语言的持续改进。Python 社区还负责开发大量开源库包,这些库包可以用来构建从动态网站到复杂数据分析应用的应用程序,以及开发基于简单 GUI 的应用程序来绘制复杂数学函数的图表。Python 大多数库包都系统地维护了从社区获得的代码,并定期更新。索引 Python 包数量最多的默认仓库是 PyPI (pypi.python.org)。PyPI 还提供了简单的方法来在您的操作系统上安装各种包,这将在下一节中介绍。
在与硬件平台一起工作时,有必要在硬件和您用于开发的计算机之间建立某种通信方式。在常见的计算机与硬件接口方法中,基于串口的通信是最受欢迎的,尤其是在 Arduino 平台上,它非常简单易建立。Python 提供了一个名为 pySerial 的库,它非常易于使用,并且快速实现串口接口。使用类似的库和 Python 的交互式编程能力,可以快速测试和实现您的项目想法。
现在,复杂的 物联网(IoT)应用程序不仅需要串行通信支持,还需要操作系统的高级功能,如 图形用户界面(GUIs),远程访问的 Web 接口,数据可视化的图表,数据分析工具,数据存储接口等。使用任何其他编程语言,如 C++ 或 Java,由于支持工具的分布式和无组织性,开发这些功能将需要大量的编程工作。幸运的是,Python 多年来在为这些类型的应用程序提供支持方面非常成功。Python 有多个库支持开发这里提到的每个功能,这些库通过 PyPI 提供。这些库是开源的,易于使用,并且得到了社区的广泛支持。这使得 Python 成为物联网应用程序的首选语言。此外,Python 还支持创建和分发您自定义构建的应用程序作为库,以便其他人也可以在他们的项目中使用它们。如果您正在为您的硬件产品开发自定义协议、API 或算法,这是一个非常有用的功能。
我们何时使用其他语言
那么,我们什么时候不应该使用 Python 来开发我们的项目呢?如前所述,Python 是一种动态语言,可以减少开发时间,但与 C、C++和 Java 等其他静态高级语言相比,它也会使您的代码执行速度变慢。这些静态语言使用编译器编译代码并创建在运行时执行的二进制文件,从而提高运行时性能。当代码的性能比更长的开发时间和更高的成本更重要时,您应该考虑这些静态语言。Python 的一些其他缺点包括内存占用大、没有适当的线程支持以及缺乏数据保护功能。简而言之,我们可以这样说,尽管 Python 提供了快速原型设计的快捷方式,但在我们完成原型测试并准备发布产品后,我们应该考虑使用其他静态高级语言进行开发。如今,这种状况正在迅速变化,公司已经开始利用 Python 来开发他们的工业产品。
注意
您可以从官方网站www.python.org获取更多与 Python 相关的信息。
安装 Python 和 Setuptools
Python 有两种版本:Python v2.x 和 Python v3.x。(在这里,x 代表适当的版本号。)虽然 Python v2.x 是一个遗留分支,并且具有更好的库支持,但 Python v3.x 是 Python 的未来。大多数 Linux 发行版和 Mac OS X 操作系统都配备了 Python,它们将 v2.x 作为首选和默认的 Python 版本。由于以下原因,本书将使用 Python v2.7 作为 Python 的默认版本:
-
这是 Python v2.x 分支的最新版本
-
它拥有庞大的社区支持,并且可以通过支持论坛获取其已知问题的解决方案
-
它被大多数主要的 Python 库支持
尽管本书提供的代码示例、练习和项目应该能在任何 Python 2.7.x 变体中运行,但拥有最新版本会更好。
安装 Python
您对操作系统的喜爱是由多种因素决定的,您永远不能忽视某人对特定操作系统的偏见。因此,本书提供了三个最受欢迎的操作系统(Linux、Mac OS X 和 Windows)的安装和配置指南。让我们首先为 Linux 计算机配置 Python。
Linux
大多数 Linux 发行版都预装了 Python。要检查已安装 Python 的最新版本,请在终端窗口中使用以下命令:
$ python -V
确保你在执行前一个命令时使用的是大写字母 V 作为选项。一旦你在终端上执行它,它将打印出你当前 Python 安装的完整版本号。如果版本是 2.7.x,那么你可以继续,你的 Linux 已经更新到了为这本书所需的 Python 的最新版本。然而,如果你有任何低于或等于 2.6.x 的版本,你将需要首先将 Python 升级到最新版本。这个过程将需要 root 权限,因为 Python 将作为一个系统组件被安装,以替换之前的版本。
Ubuntu
如果你正在使用 Ubuntu 11.10 或更高版本,你的机器上应该已经安装了 Python v2.7.x。你可以使用以下命令将 Python 升级到 v2.7.x 的最新版本:
$ sudo apt-get update && sudo apt-get --only-upgrade install python
如果你正在运行较旧的 Ubuntu 版本(如 10.04 或更早版本),你应该有 2.6 作为默认版本。在这种情况下,你需要运行以下一系列命令来安装版本 2.7:
$ sudo add-apt-repository ppa:fkrull/deadsnakes
$ sudo apt-get update
$ sudo apt-get install python2.7
第一个命令将添加一个外部 Ubuntu 仓库,这将允许你安装任何版本的 Python。下一个命令将更新和索引可用的软件包列表。最后一个命令将安装 Python 2.7 的最新版本。
Fedora 和 Red Hat
Fedora 和 Red Hat Linux 也自带了 Python 作为内置包。如果你想将 Python 的版本升级到最新版本,请在终端运行以下命令:
$ sudo yum upgrade python
小贴士
下载示例代码
你可以从你购买的所有 Packt 出版物书籍的账户中下载示例代码文件 www.packtpub.com。如果你在其他地方购买了这本书,你可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给你。
Windows
在 Windows 上安装和配置 Python 不像在 Linux 上那么简单。首先,你需要从 www.python.org/getit 下载 Python 的副本。
你需要小心你正在下载的 Python 版本。从你的 Windows 操作系统系统属性中检查操作系统是 32 位还是 64 位。在本书编写时,Python 的最新版本是 2.7.6。因此,下载 Python 的最新可用版本,但请确保它是 2.7.x 而不是 3.x。
对于许多第三方 Python 库,Windows 的安装二进制文件是为 32 位版本编译的。由于这个原因,我们建议你在 Windows 操作系统上安装 32 位版本的 Python。
如果您非常熟悉 Python 并且知道如何安装库,您可以安装 64 位版本的 Python。选择并运行下载的文件来安装 Python。尽管您可以将其安装到任何自定义位置,但建议使用默认安装位置,因为即将进行的配置步骤使用的是默认位置。安装完成后,您可以从开始菜单中找到 Python 命令行工具和 IDLE(Python GUI)。
尽管您可以从开始菜单打开这些工具进行基本脚本编写,但我们将修改 Windows 系统参数,以便通过 Windows 命令提示符访问 Python。为此,我们必须在环境变量中设置PATH,以便指向 Python 安装目录的位置。让我们通过右键单击我的电脑并选择属性来打开系统属性。否则,您也可以通过导航到开始 | 控制面板 | 系统和安全 | 系统来找到它。
您将能够看到一个类似于以下截图所示的窗口。系统窗口显示了您计算机的基本信息,包括您使用的 Windows 操作系统类型(例如 32 位或 64 位版本):

在系统窗口中,点击左侧导航栏中的高级系统设置以打开一个名为系统属性的窗口。在系统属性窗口中,点击位于窗口底部的环境变量…按钮,这将打开一个类似于以下截图所示的界面。在环境变量中,您需要更新PATH系统变量,以便将 Python 添加到默认操作系统的路径中。
点击以下截图显示的PATH选项,这将弹出一个编辑系统变量窗口。在您的现有PATH变量末尾添加C:\Python27或您自定义 Python 安装目录的完整路径。在 Python 安装路径之前必须放置一个分号(;)。如果您已经在路径变量中看到了 Python 的位置,那么您的系统已经为 Python 设置了,您不需要进行任何更改:

将 Python 添加到环境变量的主要好处是能够从命令提示符访问 Python 解释器。如果您不知道,可以通过导航到开始 | 程序 | 附件 | 命令提示符来访问 Windows 命令提示符。
Mac OS X
Mac OS X 预装了 Python,但由于操作系统的长期发布周期,默认 Python 应用程序的更新频率较慢。最新的 Mac OS X 版本,即 10.9 Maverick,配备了 Python 2.7.5,这是最新的版本:
Tests-Mac:~ test$ python
Python 2.7.5 (default, Aug 25 2013, 00:04:04)
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
之前的版本,如 Mac OS X 10.8 Mountain Lion 和 Mac OS X 10.7 Lion 分别包含了 Python 2.7.2 和 Python 2.7.1,这些也是本书的兼容版本。如果你是一位经验丰富的 Python 用户或者想要使用 Python 最新版本的人,你可以从 www.python.org/getit 下载最新版本。
旧版本的 Mac OS X,如 Snow Leopard 及更高版本,它们附带了一个较旧的 Python 版本,可以通过从 www.python.org/getit 下载和安装来更新到最新版本。
安装 Setuptools
Setuptools 是一个包含构建和分发 Python 包的实用工具集合的库。这个集合中最重要的工具被称为 easy_install。它允许用户查看我们之前提到的 PyPI,即 Python 包仓库,并提供一个简单的接口通过名称安装任何包。easy_install 实用工具会自动下载、构建、安装和管理用户所需的包。这个实用工具在本书的后期部分被用来安装 Python 和 Arduino 即将到来的项目所需的必要包。尽管 easy_install 被用作安装 Python 包的简单方式,但它缺少了一些有用的功能,如跟踪操作、支持卸载和支持其他版本控制系统。近年来,Python 社区开始采用另一个名为 pip 的工具来替代 easy_install,它支持这些功能。由于 easy_install 和 pip 都使用相同的 PyPI 仓库,因此从现在开始,你可以使用这些实用工具中的任何一个来安装所需的 Python 包。
为了缩小范围,我们将专注于安装 Setuptools 以及与之一起安装的默认实用工具,即 easy_install。在本节稍后,我们还将安装 pip,以防你也需要使用它。让我们首先从为各种操作系统安装 Setuptools 开始。
Linux
在 Ubuntu 中,Setuptools 可在默认仓库中找到,可以使用以下命令进行安装:
$ sudo apt-get install python-setuptools
对于 Fedora,可以使用默认的软件管理器 yum 进行安装:
$ sudo yum install python-setuptools
对于其他 Linux 发行版,可以使用以下单行脚本下载和构建:
$ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | sudo python
一旦在你的 Linux 发行版上安装了 Setuptools,easy_install 可以直接从终端作为内置命令访问。
Windows
与 Linux 相比,Setuptools 的安装对 Windows 来说并不那么直接。它要求用户从 pypi.python.org/pypi/setuptools 的 Windows 部分下载 ez_setup.py 文件。
下载完成后,在下载 ez_setup.py 文件的文件夹中按住 Shift 键并右键点击。选择 在此处打开命令窗口 并执行以下命令:
> python ez_setup.py
这将在您的默认 Python 安装文件夹的Scripts文件夹中安装 Setuptools。使用我们添加 Python 到环境变量时使用的方法,现在通过将C:\Python27\Scripts添加到PATH,后面跟着分号(;)来包含 Setuptools。
这将使您能够使用easy_install将各种 Python 包安装到名为Libs的 Python 包文件夹中。一旦您将包管理器添加到环境变量中,您需要关闭并重新打开命令提示符以使这些更改生效。
Mac OS X
Setuptools 可以使用以下任何一种方法在 Mac OS X 中安装。对于初学者来说,建议使用第一种方法,因为第二种方法需要外部包管理器 Homebrew。
如果您之前从未使用过 Homebrew,您需要按照以下步骤在您的 Mac 上安装 Setuptools:
-
从
pypi.python.org/pypi/setuptools的 Unix/Mac 部分下载ez_setup.py。 -
打开终端并导航到您下载此文件的目录。对于大多数浏览器,文件会被保存在
下载文件夹中。 -
在终端中运行以下命令以构建和设置 Setuptools:
$ sudo python ez_setup.py
如果您熟悉基于 Homebrew 的软件安装,只需按照以下快速步骤安装 Setuptools:
-
首先,如果您还没有安装
wget,请从 Homebrew 安装它:$ brew install wget -
一旦您安装了
wget,请在终端中运行以下命令:$ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | python注意
更多关于 Homebrew 实用工具的信息可以从
brew.sh获取。您可以通过在终端中运行以下简单脚本来在您的 Mac 上安装 Homebrew:
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
安装 pip
由于您已成功安装 Setuptools,让我们使用它来安装pip。对于 Linux 或 Mac OS X,您可以在终端中运行以下命令来安装pip:
$ sudo easy_install pip
对于 Windows,打开命令提示符并执行以下命令:
> easy_install.exe pip
如果您已经在您的计算机上安装了pip,请确保您将其升级到最新版本以克服与升级相关的一些错误。您可以在终端使用以下命令升级pip:
$ sudo easy_install --upgrade pip
由于您已经使用easy_install安装了一个 Python 包,让我们更熟悉 Python 包管理。
安装 Python 包
安装pip后,您有两个不同的选项来安装 PyPi 存储库上列出的任何第三方 Python 包(pypi.python.org)。以下是需要了解的用于处理 Python 包安装的各种程序。在以下示例中,术语PackageName是一个用于您想要与之工作的 Python 包的伪名称。对于您选择的包,从 PyPi 网站识别适当的包名并将其名称放在PackageName的位置。在某些情况下,您需要 root(超级用户)权限来安装或卸载包。在这种情况下,您可以使用sudo后跟适当的命令。
要安装 Python 包,请在终端中执行以下命令:
$ easy_install PackageName
否则,您还可以执行以下命令:
$ pip install PackageName
如果您想安装特定版本的包,可以使用以下命令:
$ easy_install "PackageName==version"
如果您不知道确切的版本号,您也可以使用比较运算符,如>、<、>=或<=来指定版本号的范围。easy_install和pip都会从存储库中选择最佳匹配的包版本并安装:
$ easy_install "PackageName > version"
同时,对于pip,您可以使用以下相同的命令执行类似操作:
$ pip install PackageName==version
$ pip install "PackageName>=version"
例如,如果您想安装 1.0 到 3.0 之间的版本,您需要使用以下命令:
$ pip install "PackageName>=0.1,<=0.3"
使用easy_install或pip升级包非常简单。两者使用的命令选项也非常相似:
$ easy_install --upgrade PackageName
$ pip install --upgrade PackageName
虽然easy_install不支持包的干净卸载,但您可以使用以下命令确保 Python 停止搜索指定的包。之后,请从安装目录中仔细删除包文件:
$ easy_install -mxN PackageName
清理卸载大多数包的更好方法是使用pip而不是easy_install:
$ pip uninstall PackageName
在 PyPI 网站pypi.python.org/上可以找到 Setuptools 支持的 Python 包的详细列表。
Python 编程的基础知识
如果您有使用其他任何编程语言的经验,Python 入门非常容易。如果您以前从未编程过,本节将带您了解 Python 的一些基础知识。如果您已经使用过 Python,您应该跳过本节,继续下一节。
假设设置说明正确无误,让我们通过在终端或命令提示符中执行 Python 命令来打开 Python 解释器。您应该得到以下截图显示的类似结果。如果您是从网站上下载设置文件安装的 Python,那么您也应该已经安装了 Python 集成开发环境(IDLE)。您也可以通过打开其安装位置处的 IDLE 来启动 Python 解释器。
正如您所看到的,在打印一些系统信息后,解释器打开了一个带有三个大于号(>>>)的提示符,这也被称为主要提示符。现在解释器处于交互模式,并准备好从提示符执行脚本。

要关闭 Python 解释器的交互模式,请在主要提示符下运行exit()或quit(),或者使用键盘快捷键Ctrl + D。
注意
注意,Python 的内置函数是区分大小写的。这意味着以下情况:
exit() ≠ EXIT() ≠ Exit()
官方 Python 网站为初学者提供了全面的教程,帮助他们开始 Python 编程。如果您正在寻找详细编程教程,而不是即将到来的简要概述,强烈建议您访问官方 Python 教程docs.python.org/2/tutorial/index.html。
Python 运算符和内置类型
现在您对 Python 提示符有了初步的了解,让我们让您熟悉一些基本的 Python 命令。对于这些练习,我们将使用 Python IDLE,它也以 Python 交互提示符打开。在编写大型和复杂代码时,您需要一种方法来描述代码段、任务和注释。在所有编程语言中,不可执行的内容被称为注释,在 Python 中,它们以井号字符(#)开头。像注释一样,您将经常需要通过在提示符中使用 print 命令来检查输出:
>>> # Fundamental of Python
>>> # My first comment
>>> name = "John" # This is my name
>>> print name
John
注意
除了 IDLE,您还可以从终端访问 Python 交互式提示符。当从终端使用 Python 时,请确保您正确处理缩进。
运算符
Python 支持直接从解释器使用基本数学运算符,如+、-、*和/。使用这些运算符,您可以在提示符中执行基本计算,如下面的示例所示。尝试在您的提示符中执行这些操作,以便开始使用 Python 解释器作为计算器:
>>> 2 + 2
4
>>> (2*3) + 1
7
>>> (2*3) / 5
1
注意
当使用 Python 解释器时,建议您遵循 Python 代码风格指南,这通常也被称为 PEP-8 或 pep8。有关 PEP-8 的更多信息,请访问www.python.org/dev/peps/pep-0008/。
内置类型
Python 是一种动态类型语言,这意味着在初始化变量时,您不必显式声明变量的类型。当您给变量赋值时,Python 解释器会自动推断数据类型。例如,让我们在解释器的交互模式下声明以下变量:
>>> weight = height = 5
>>> weight * height
25
>>> type(weight)
<type 'int'>
在将值赋给 weight 变量时,我们没有指定数据类型,但 Python 解释器将其赋值为整型,int。解释器将 int 类型分配的原因是数值不包含任何小数点。现在让我们声明一个包含小数点的值的变量。可以使用 type() 内置函数来找出指定变量的数据类型:
>>> length = 6.0
>>> weight * height * length
150.0
>>> type(length)
<type 'float'>
如你所见,解释器将数据类型分配为 float。解释器还可以推断复数的类型,如下面的示例所示。你可以使用点(.)运算符后跟 real 和 imag 来访问复数的实部和虚部:
>>> val = 2.0 + 3.9j
>>> val.real
2.0
>>> val.imag
3.9
为了更好地与复数进行交互,让我们尝试以下示例中的 abs() 和 round() 函数。它们是用于获取绝对值和四舍五入数字的内置 Python 函数:
>>> abs(val)
4.382921400162225
>>> round(val.imag)
4.0
像数字一样,Python 解释器也可以自动识别字符串数据类型的声明。在 Python 中,字符串值使用单引号或双引号包围的值来赋值。当解释器看到任何用引号包围的值时,它将其视为字符串。Python 支持使用 + 运算符来连接字符串:
>>> s1 = "Hello"
>>> s2 = "World!"
>>> s1 + s2
'HelloWorld!'
>>> s1 + " " + s2
'Hello World!'
字符类型是大小为 1 的字符串,字符串的各个字符可以通过使用索引数字来访问。字符串的第一个字符索引为 0。通过以下脚本进行实验,以了解 Python 中的索引(下标):
>>> s1[0]
'H'
>>> s1[:2]
'He'
>>> s1 + s2[5:]
'Hello!'
注意
与默认符号 >>> 的主提示符类似,Python 交互式解释器在从终端使用时也有一个二级提示符,使用三个点(…)。当你使用二级提示符时,在 IDLE 中你将看不到这三个点。二级提示符用于多行结构,需要连续的行。通过手动在解释器中输入以下命令来执行,并且不要忘记在 if 语句之后用制表符缩进下一行:
>>> age = 14
>>> if age > 10 or age < 20:
... print "teen"
teen
数据结构
Python 支持四种主要数据结构(list、tuple、set 和 dictionary),并且围绕这些数据结构有许多重要的内置方法。
列表
列表用于将单个或多个数据类型的值组合在一起。list 结构可以通过在方括号中声明值并使用逗号(,)作为分隔符来赋值:
>>> myList = ['a', 2, 'b', 12.0, 5, 2]
>>> myList
['a', 2, 'b', 12.0, 5, 2]
像字符串一样,列表中的值可以使用索引数字来访问,索引从 0 开始。Python 使用冒号运算符的切片功能来获取数据结构中的特定子集或元素。在标准格式中,切片可以使用 myList[start:end:increment] 语法指定。以下是一些示例,以更好地理解切片的概念:
-
你可以如下访问列表中的一个元素:
>>> myList[0] 'a' -
你可以通过使用空起始和结束值来访问列表中的所有元素:
>>> myList[:] ['a', 2, 'b', 12.0, 5, 2] -
您可以提供起始和结束索引值以获取列表的特定子集:
>>> myList[1:5] [2, 'b', 12.0, 5] -
使用减号和索引数字的组合告诉解释器使用该索引数字的反向。在以下示例中,
-1反向实际上代表索引数字5:>>> myList[1:-1] [2, 'b', 12.0, 5] -
您可以通过提供起始值和结束值来获取列表的每个其他元素:
>>> myList[0:5:2] ['a', 'b', 5] -
您可以使用
len()方法检查列表变量的长度。此方法在即将到来的项目中将非常有用:>>> len(myList) 6 -
您还可以执行各种操作以在现有列表中添加或删除元素。例如,如果您想在列表末尾添加一个元素,请使用列表上的
append()方法:>>> myList.append(10) >>> myList ['a', 2, 'b', 12.0, 5, 2, 10] -
要在特定位置添加元素,您可以使用
insert(i, x)方法,其中i表示索引值,而x是您想要添加到列表的实际值:>>> myList.insert(5,'hello') >>> myList ['a', 2, 'b', 12.0, 5, 'hello', 2, 10] -
同样,您可以使用
pop()从列表中删除元素。简单的pop()函数将删除列表的最后一个元素,而使用pop(i)可以删除特定位置的元素,其中i是索引数字:>>> myList.pop() 10 >>> myList ['a', 2, 'b', 12.0, 5, 'hello', 2] >>> myList.pop(5) 'hello' >>> myList ['a', 2, 'b', 12.0, 5, 2]
元组
元组是 Python 支持的不可变数据结构(与列表的可变结构不同)。不可变数据结构意味着您不能从元组数据结构中添加或删除元素。由于它们的不可变属性,元组比列表访问更快,通常用于存储一组不变的值,这些值永远不会改变。
tuple 数据结构声明方式与 list 相同,但使用括号或没有任何括号:
>>> tupleA = 1, 2, 3
>>> tupleA
(1, 2, 3)
>>> tupleB = (1, 'a', 3)
>>> tupleB
(1, 'a', 3)
就像在 list 数据结构中一样,tuple 中的值可以使用索引数字访问:
>>> tupleB[1]
'a'
由于元组是不可变的,因此列表操作方法(如 append()、insert() 和 pop())不适用于元组。
集合
Python 中的 set 数据结构实现用于支持数学集合操作。set 数据结构包括一个无序的元素集合,没有重复项。由于其数学用例,此数据结构主要用于在列表中查找重复项,因为使用 set() 函数将列表转换为集合会从列表中删除重复项:
>>> listA = [1, 2, 3, 1, 5, 2]
>>> setA = set(listA)
>>> setA
set([1, 2, 3, 5])
字典
dict 数据结构用于通过键索引存储键值对,在其他语言中也被称为关联数组、散列或哈希表。与其他数据结构不同,dict 的值可以通过关联键提取:
>>> boards = {'uno':328,'mega':2560,'lily':'128'}
>>> boards['lily']
'128'
>>> boards.keys()
['lily', 'mega', 'uno']
注意
您可以在 docs.python.org/2/tutorial/datastructures.html 了解更多关于 Python 数据结构和相关方法的信息。
控制程序流程
就像任何其他语言一样,Python 支持使用复合语句控制程序流程。在本节中,我们将简要介绍这些语句。您可以从官方 Python 文档docs.python.org/2/reference/compound_stmts.html中获取有关它们的详细信息。
if语句
if语句是最基本和最标准的语句,用于设置条件流程。为了更好地理解if语句,请在 Python 解释器中执行以下代码,并使用不同的age变量值:
>>> age = 14
>>> if age < 18 and age > 12:
print "Teen"
elif age < 13:
print "Child"
else:
print "Adult"
这将在解释器上打印出Teen。
for语句
Python 的for语句按照序列中元素的顺序遍历任何序列的元素:
>>> celsius = [13, 21, 23, 8]
>>> for c in celsius:
print " Fahrenheit: "+ str((c * 1.8) + 32)
这将导致 Python 解释器生成以下输出,显示从给定的摄氏值计算出的华氏值:
Fahrenheit: 55.4
Fahrenheit: 69.8
Fahrenheit: 73.4
Fahrenheit: 46.4
while语句
while语句用于在 Python 程序中创建连续循环。while循环会持续迭代代码块,直到条件被证明为真:
>>> count = 5
>>> while (count > 0):
print count
count = count - 1
while语句将不断迭代并打印变量count的值,同时将其值减 1,直到条件(即count > 0)为真。一旦count的值低于或等于 0,while循环将退出代码块并停止迭代。
Python 支持的其他复合语句是try/catch和with。这些语句将在接下来的章节中详细介绍。Python 还提供了循环控制语句,如break、continue和pass,可以在使用前面提到的复合语句执行循环时使用。您可以从docs.python.org/2/tutorial/controlflow.html了解更多有关这些 Python 特性的信息。
内置函数
Python 支持许多有用的内置函数,这些函数不需要导入任何外部库。我们已经根据它们的函数特性将这些函数描述为各自类别的集合。
转换
转换方法,如int()、float()和str()可以将其他数据类型分别转换为整数、浮点型或字符串数据类型:
>>> a = 'a'
>>> int(a,base=16)
10
>>> i = 1
>>> str(i)
'1'
类似地,可以使用list()、set()和tuple()将一种数据结构转换为另一种数据结构。
数学运算
Python 还支持内置的数学函数,可以从列表中找到最小值和/或最大值。查看以下示例,并尝试不同的数据结构来理解这些方法:
>>> list = [1.12, 2, 2.34, 4.78]
>>> min(list)
1.12
>>> max(list)
4.78
pow(x,y)函数返回x的y次幂:
>>> pow(3.14159, 2)
9.869587728099999
字符串操作
Python 通过内置函数提供对字符串操作的简单访问,这些函数针对性能进行了优化。让我们看看以下示例:
-
用于替换字符串或子字符串出现的代码:
>>> str = "Hello World!" >>> str.replace("World", "Universe") 'Hello Universe!' -
用于使用分隔字符分割字符串的代码,其中默认字符是空格:
>>> str = "Hello World!" >>> str.split() ['Hello', 'World!'] -
用于使用任何其他分隔字符分割字符串的代码:
>>> str2 = "John, Merry, Tom" >>> str2.split(",") ['John', ' Merry', ' Tom'] -
用于将整个字符串值转换为大写或小写的代码:
>>> str = "Hello World!" >>> str.upper() 'HELLO WORLD!' >>> str.lower() 'hello world!'注意
官方网站上的 Python 文档详细介绍了每个内置函数及其示例。为了更好地理解 Python 编程,请访问
docs.python.org/2/library/functions.html。
Arduino 简介
任何需要计算或与其他计算机接口的电子产品,首先需要使用简单工具快速原型化其概念。Arduino 是一个围绕流行的微控制器系列设计的开源硬件原型平台,它包括一个简单的软件开发环境。除了原型化之外,你还可以使用 Arduino 来开发自己的自己动手做(DIY)项目。Arduino 通过让你简单地将传感器和执行器与计算机连接起来,将计算世界与物理世界连接起来。基本上,你可以通过使用 Arduino 的输入/输出引脚和微控制器来编写代码,监控和控制你日常生活中的各种电子组件。这些组件的例子包括电机、恒温器、灯光、开关等等。
历史
2005 年,Arduino 的意大利联合创始人 Massimo Banzi 为他在互动设计学院伊夫雷亚(IDII)的学生开发了这项技术。从那时起,Arduino 已经发展成为最大的开源硬件平台之一。Arduino 设计的所有软件组件和原理图都是开源的,你可以以非常低的价格购买硬件——大约 30 美元——或者你也可以自己制作它。
为什么选择 Arduino?
Arduino 社区的主要目标是持续改进 Arduino 平台,以下是一些考虑的目标:
-
Arduino 平台应该是一个经济实惠的平台
-
它应该易于使用和编码
-
它应该是一个开源和可扩展的软件平台
-
它应该是一个开源和可扩展的硬件平台
-
它应该有社区支持的 DIY 项目
这些简单但强大的目标使 Arduino 成为一种流行的广泛使用的原型平台。Arduino 使用基于流行的 AVR 硬件架构的 Atmel ATmega 系列微控制器。对 AVR 架构的巨大支持也使 Arduino 成为首选的硬件平台。以下图像显示了 Arduino 板的基本版本,称为 Arduino Uno(在意大利语中,Uno 意味着“一个”):

Arduino 变体
任何项目,硬件需求都是由项目规格决定的。如果你正在开发一个需要与大量外部组件接口的项目,你需要一个具有足够数量的输入/输出(I/O)引脚的实验平台来进行接口。如果你正在从事一个需要进行大量复杂计算的项目,你需要一个计算能力更强的平台。
幸运的是,Arduino 板有 16 种不同的官方版本,每个版本的 Arduino 在形态、计算能力、I/O 引脚和其他板载功能方面与其他版本不同。Arduino Uno 是基本且最受欢迎的版本,对于简单的 DIY 项目来说已经足够。对于本书中的大多数练习,我们将使用 Arduino Uno 板。你也可以使用另一个流行的变体,即 Arduino Mega,这是一个更大的板,具有额外的引脚和强大的微控制器。以下表格显示了 Arduino 板一些更受欢迎和活跃的变体的比较:
| 名称 | 处理器 | 处理器频率 | 数字 I/O | 数字 I/O(带 PWM) | 模拟 I/O |
|---|---|---|---|---|---|
| Arduino Uno | ATmega328 | 16 MHz | 14 | 6 | 6 |
| Arduino Leonardo | ATmega32u4 | 16 MHz | 14 | 6 | 12 |
| Arduino Mega | ATmega2560 | 16 MHz | 54 | 14 | 16 |
| Arduino Nano | ATmega328 | 16 MHz | 14 | 6 | 8 |
| Arduino Due | AT91SAM3X8E | 84 MHz | 54 | 12 | 12 |
| LilyPad Arduino | ATmega168v 或 ATmega328v | 8 MHz | 14 | 6 | 6 |
这些任何一种变体都可以使用一个称为Arduino IDE的通用集成开发环境进行编程,这将在下一节中描述。你可以根据项目需求选择这些 Arduino 板中的任何一个,Arduino IDE 应该能够编译并将程序下载到板子上。
Arduino Uno 板
由于 Arduino Uno 将是本书中大多数项目的实际板,让我们熟悉一下该板。Uno 板最新版本基于 Atmel 的 ATmega328 微控制器。该板将微控制器的 I/O 引脚扩展到外围,然后可以通过电线利用这些引脚来接口组件。该板共有 20 个引脚用于接口,其中 14 个是数字 I/O 引脚,6 个是模拟输入引脚。在 14 个数字 I/O 引脚中,6 个引脚也支持脉冲宽度调制(PWM),这支持对连接组件的功率进行控制性传输。
该板在 5V 电压下工作。数字 I/O 引脚的最大电流额定值为 40 mA,足以驱动大多数 DIY 电子元件,但不包括对高电流有要求的电机。
尽管前面的图像提供了 Uno 板的概述,但以下图表描述了 Uno 板上的引脚。如图所示,数字引脚位于板的一侧,而模拟引脚位于另一侧。板子上还有几个电源引脚,可以用来为外部组件提供 5V 和 3.3V 的电源。板子的两侧也有地线引脚。我们将广泛使用 5V 的电源和地线引脚来完成我们的项目。数字引脚 D0 和 D1 分别通过 Tx(传输)和 Rx(接收器)接口支持串行接口。板上的 USB 端口可以用来将 Arduino 连接到计算机。

既然我们已经熟悉了 Arduino 硬件,让我们继续学习如何编程 Arduino 板。
安装 Arduino IDE
开始熟悉 Arduino 的第一步是安装 Arduino 集成开发环境(IDE)。根据 Python 安装部分开始时选择的操作系统,遵循适当的子部分来安装正确的 IDE。
Linux
在 Ubuntu 上安装 Arduino IDE 非常简单。Ubuntu 仓库已经包含了带有所需依赖项的 Arduino IDE。
对于 Ubuntu 12.04 或更新的版本,请在终端中执行以下命令来安装 Arduino:
$ sudo apt-get update && sudo apt-get install arduino arduino-core
Ubuntu 仓库中 Arduino IDE 的最新版本是 1.0.3。有关其他 Ubuntu 相关问题的更多信息,请参阅playground.arduino.cc/Linux/Ubuntu。
对于 Fedora 17 或更新的 Red Hat Linux 版本,请在终端中执行以下脚本:
$ sudo yum install arduino
可以在playground.arduino.cc/Linux/Fedora获取 Fedora 的附加安装问题的答案。
Mac OS X
要在 Mac OS X(10.7 或更高版本)上安装 Arduino IDE,请执行以下步骤:
-
从
arduino.cc/en/Main/Software下载适用于 Mac OS X 的最新版本的 Arduino IDE,当本书编写时,版本是 1.0.5。 -
解压并将 Arduino 拖到应用程序文件夹中。
Arduino IDE 是用 Java 编写的,需要您的计算机配备适当的 Java 版本。从您的应用程序中打开 IDE。如果您在 Mac 上没有安装 Java,程序将弹出一个窗口并要求您安装 Java SE 6 运行时。按照要求安装 Java(因为 OS X 会自动为您安装)。
Windows
Windows 上 Arduino 的安装非常简单。从arduino.cc/en/Main/Software下载设置文件。选择 Arduino IDE 的最新版本,即 1.0.x 或更新的版本。
确保根据您的操作系统下载适当的 Arduino IDE 版本,即 32 位或 64 位。按照安装向导中指定的默认位置安装 IDE。安装完成后,您可以通过导航到开始 | 程序来打开 IDE。
开始使用 Arduino IDE
Arduino IDE 是一个使用 Java 开发的跨平台应用程序,可以用来开发、编译和上传程序到 Arduino 板。启动 Arduino IDE 后,您将看到一个类似于以下截图所示的界面。IDE 包含一个用于编码的文本编辑器、一个菜单栏以访问 IDE 组件、一个工具栏以方便访问最常用的功能,以及一个文本控制台以检查编译器的输出。底部状态栏显示所选的 Arduino 板及其连接的端口号,如下所示:

什么是 Arduino 草图?
使用 IDE 开发的 Arduino 程序称为草图。草图是用 Arduino 语言编写的,该语言基于 C/C++的定制版本。一旦您在内置文本编辑器中编写完代码,您可以使用.ino扩展名保存它。当您保存这些草图文件时,IDE 会自动创建一个文件夹来存储它们。如果您为草图使用任何其他支持文件,例如头文件或库文件,它们都将存储在此位置(也称为草图簿)。
要打开一个新的草图簿,打开 Arduino IDE 并从文件菜单中选择新建,如下面的截图所示:

您将看到一个空白的文本编辑器。文本编辑器支持标准功能(即,复制/粘贴、选择、查找/替换等)。在我们继续 Arduino 程序之前,让我们探索 IDE 提供的其他工具。
注意
在 1.0 版本之前的 Arduino IDE 使用.pde扩展名来保存草图簿。从 1.0 版本开始,它们使用.ino扩展名保存。您仍然可以在最新版本的 IDE 中打开具有.pde扩展名的文件。稍后,当您保存它们时,IDE 将将其转换为.ino扩展名。
使用库
Arduino IDE 使用库来扩展现有草图的功能。库是一组组合在一起以执行围绕特定组件或概念的任务的函数。大多数内置的 Arduino 库提供与外部硬件组件开始工作的方法。您可以通过导航到草图 | 导入库…来导入任何库,如下面的截图所示:

您也可以通过在草图的开头使用#include语句指定库来为您的草图使用库,即#include <Wire.h>。
Arduino IDE 还提供了添加支持特定硬件或提供额外功能的库的能力。在接下来的章节中,我们将处理一些这些外部库,并且我们将在那时介绍导入它们的过程。
你可以从arduino.cc/en/Reference/Libraries了解更多关于内置 Arduino 库的信息。
使用 Arduino 示例
Arduino IDE 包含大量的内置示例草图。这些示例旨在让用户熟悉基本的 Arduino 概念和内置 Arduino 库。Arduino 社区对这些示例进行了良好的维护,因为它们通过 Arduino 网站(arduino.cc/en/Tutorial/HomePage)为每个示例提供了全面的支持。在 Arduino IDE 中,你可以通过导航到文件 | 示例来访问这些示例,如下面的截图所示:

让我们从简单的内置示例开始。通过导航到文件 | 示例 | 01.基础 | 闪烁来打开闪烁示例。IDE 将打开一个新窗口,其中包含与以下程序类似的代码:
/*
Blink
Turns on an LED on for one second, then off for one second, repeatedly.
This example code is in the public domain.
*/
// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;
// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
}
// the loop routine runs over and over again forever:
void loop() {
digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
这个 Arduino 草图被设计用来在数字引脚 13 上闪烁一个 LED。你可能想知道为什么我们没有讨论或要求你带来任何硬件。那是因为 Arduino Uno 板子上配备了一个连接到数字引脚 13 的板上 LED。现在,我们不再深入 Arduino 代码,而是将重点放在通过 IDE 处理 Arduino 板的过程上。
编译和上传草图
一旦你在 IDE 中打开了代码,你需要做的第一件事就是选择你将要上传草图的 Arduino 板类型。Arduino IDE 需要知道板子的类型,以便为适当的微控制器编译程序,因为不同的 Arduino 板可能具有不同的 Atmel 微控制器。因此,在你继续编译或上传程序到板子之前,你需要执行这一步。
你可以通过导航到工具 | 板来选择 Arduino 板,如下面的截图所示:

从板子列表中选择 Arduino Uno,除非你使用的是不同的 Arduino 板。一旦你选择了板子,你就可以继续编译草图。你可以通过导航到菜单栏中的草图 | 验证/编译来编译草图,或者使用键盘快捷键Ctrl + R。如果一切设置得当,你应该能够编译代码而不会出现任何错误。
在成功编译草图后,是时候将编译好的代码上传到 Arduino 板上了。为此,您需要确保您的 Arduino IDE 已经正确连接到您的计算机。如果尚未连接,请使用 USB 端口将 Arduino 板连接到您的计算机。现在,您需要让 IDE 知道板连接的串行端口。导航到工具 | 串行端口并选择合适的串行端口。
注意
在某些 Linux 发行版的情况下,由于串行端口上的权限限制,您可能无法看到或上传 Arduino 程序到板子上。在终端运行以下命令应该可以解决这个问题:
$ sudo usermod -a -G uucp, dialout, lock <username>
您现在可以通过导航到文件 | 上传将编译好的草图上传到您的 Arduino 板。这个过程将使用串行连接在微控制器中烧录编译好的固件。请等待一段时间,或者直到板上的 LED(Tx 和 Rx LED)停止闪烁。现在,您的 Arduino 板已经准备好了,您的第一个草图也已经上传。您可以观察靠近数字引脚 13 处闪烁的 LED 的性能。
使用串行监视器窗口
在前面的过程中,我们使用通用串行总线(USB)电缆将您的 Arduino 板连接到计算机的 USB 端口。USB 端口是一个工业标准,用于通过串行接口连接各种电子组件到计算机。当您使用 USB 连接 Arduino 板时,计算机实际上将其作为串行外围设备进行接口。在整个书中,我们将使用 USB 连接称为串行连接。串行监视器窗口是 Arduino IDE 的一个内置实用工具。可以通过导航到工具 | 串行监视器或使用Ctrl + Shift + M键盘快捷键访问串行监视器窗口。它可以配置为观察通过连接 Arduino 板到计算机的串行端口发送或接收的数据。您还可以使用下拉菜单选项设置串行通信的波特率。当测试原型及其性能时,这个实用工具(在本书的后续部分)将非常有用。
Arduino 编程简介
Arduino 平台被引入是为了简化每个人的电子硬件原型设计。因此,Arduino 编程旨在让非程序员,如设计师、艺术家和学生,容易学习。Arduino 语言是用 C/C++实现的,而草图和程序结构的基本原理来源于一个名为Processing的开源编程语言和一个名为Wiring的开源电子原型设计语言。
注释
Arduino 采用了从 C 语言继承的注释格式,并且与高级语言类似;然而,它与我们在本章 earlier 学习的 Python 注释格式不同。有各种注释方法,如下所示:
-
块注释: 这是通过在
/*和*/之间覆盖注释文本来实现的:/* This is a comment. * Arduino will ignore any text till it finds until the ending comment syntax, which is, */ -
单行或内联注释: 这是通过在行前使用
//来实现的:// This syntax only applies to one line. // You have to use it again for each next line of comment. int pin = 13; //Selected pin 13
通常,在草图开头的块注释主要用于描述整个程序。单行注释用于描述特定的函数或待办事项,如下所示:
//TODO: explain variables next.
变量
与任何其他高级语言一样,变量用于通过三个组件来存储数据:名称、值和数据类型。例如,考虑以下语句:
int pin = 10;
在这里,pin 是一个使用 int 类型定义的变量名,并持有值 10。在代码的后续部分,所有 pin 变量的出现都将从我们刚才在这里做出的声明中检索数据。只要第一个字符不是数字,你可以使用任何 alpha-numeric 字符组合来选择变量名。
常量
在 Arduino 语言中,常量是预定义的变量,用于简化程序:
-
HIGH, LOW: 在与 Arduino 板上的数字引脚一起工作时,这些引脚上只有两种不同的电压状态是可能的。如果一个引脚被用来获取输入,任何超过 3V 的测量值都被认为是HIGH状态。如果你使用一个引脚作为输出,那么HIGH状态将设置引脚电压为 5V。相反的电压级别被认为是LOW状态。 -
false, true: 这些用于表示逻辑上的真和假级别。false被定义为0,而true通常被定义为1。 -
INPUT, OUTPUT: 这些常量用于定义 Arduino 引脚的角色。如果你将 Arduino 引脚的模式设置为INPUT,Arduino 程序将准备引脚来读取传感器。同样,OUTPUT设置将引脚准备为向连接的传感器提供足够的电流。
我们将在本书的后续部分使用这些常量,并使用示例代码来解释它们。
数据类型
每个自定义变量的声明都需要用户指定与变量关联的数据类型。Arduino 语言使用一组标准的数据类型,这些数据类型在 C 语言中使用。以下列出了这些数据类型及其描述:
-
void: 这在函数声明中使用,表示该函数不会返回任何值:void setup() { // actions } -
布尔型: 使用数据类型布尔型定义的变量只能持有两个值之一,true或false:boolean ledState = false; -
字节型: 这用于存储一个 8 位无符号数,基本上是从 0 到 255 的任何数字:byte b = 0xFF; -
int: 这是整数的简称。它存储 16 位(Arduino Uno)或 32 位(Arduino Due)数字,并且是 Arduino 语言的主要数字存储数据类型之一。尽管本书中将使用int来声明数字,但 Arduino 语言还有long和short数字数据类型用于特殊情况:int varInt = 2147483647; long varLong = varInt; short varShort = -32768; -
float: 这种数据类型用于有小数点的数字。这些也被称为浮点数。float是在 Arduino 语言中表示数字的更广泛使用的数据类型之一,与int一起使用:float varFloat = 1.111; -
char: 这种数据类型存储一个字符值,并占用 1 字节内存。当为char数据类型提供值时,字符字面量用单引号声明:char myCharacater = 'P'; -
array:array存储一组变量集合,可以通过索引号访问。如果你熟悉 C/C++ 中的数组,那么开始使用 Arduino 语言将更容易,因为 Arduino 语言使用相同的 C/C++ 数组。以下是一些初始化数组的示例方法:int myIntArray[] = {1, 2, 3, 4, 5}; int tempValues[5] = { 32, 55, 72, 75}; char msgArray[10] = "hello!";可以使用索引号(索引从数字 0 开始)访问数组:
myIntArray[0] == 1 msgArray[2] == 'e'
转换
转换函数用于将任何数据类型值转换为提供的数据类型。Arduino 语言实现了以下转换函数,这些函数可以在编程过程中使用:
-
char(): 这将任何数据类型的值转换为字符数据类型 -
byte(): 这将任何数据类型的值转换为字节数据类型 -
int(): 这将任何数据类型的值转换为整数数据类型 -
float(): 这将任何数据类型的值转换为浮点数数据类型
作为使用这些函数的演示,请查看以下示例:
int myInt = 10;
float myfloat = float(myInt);
前述代码的实现将创建一个浮点变量 myFloat,其值为 10.0,使用由 myInt 变量初始化的整数值。
函数和语句
函数,也称为子程序或过程,是一段用于执行特定任务的代码。Arduino 语言提供了一些预定义的函数,用户也可以编写自定义函数以实现特定的程序逻辑。这些自定义函数可以从脚本的任何部分调用以执行特定任务。函数有助于程序员简化调试,减少错误发生的可能性,并组织编码概念:
void blinkLED(){
// action A;
// action B;
}
Arduino 语言提供了一套库函数,用于简化编程体验。尽管并非所有这些库函数都是 Arduino 脚本所必需的,但 setup() 和 loop() 是强制性的函数,并且它们对于成功编译脚本是必需的。
setup() 函数
当 Arduino 运行一个草图时,它首先查找setup()函数。setup()函数用于在程序的其他部分之前执行重要的编程子例程,例如声明常量、设置引脚、初始化串行通信或初始化外部库。当 Arduino 运行程序时,它只执行一次setup()函数。如果你查看我们在上一节中使用的闪烁草图,你可以看到setup()函数的初始化,如下面的代码片段所示:
void setup() {
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
}
正如你在我们的示例中看到的那样,我们使用了pinMode()函数在setup()函数中分配 LED 引脚的角色。
loop()函数
一旦 Arduino 执行了setup()函数,它就开始连续迭代loop()函数。setup()函数包含初始化参数,而loop()函数包含你程序的逻辑参数:
void loop() {
digitalWrite(led, HIGH);
delay(1000);
digitalWrite(led, LOW);
delay(1000);
}
正如你在前面的代码片段中看到的那样,闪烁草图中的loop()函数执行了主要的代码,该代码使 LED 闪烁并重复迭代过程。
pinMode()函数
pinMode()函数用于设置 Arduino 的行为。正如我们在闪烁草图的setup()函数中看到的那样,pinMode()函数配置 LED 引脚为OUTPUT:
pinMode(led, OUTPUT)
在这里,led变量被分配到数字引脚 13,其模式将由pinMode()函数更改。
处理引脚
一旦你完成了配置程序将使用的引脚,你还需要帮助读取这些引脚的输入或向它们发送信号。Arduino 提供了一些特定的函数来处理这些场景:
-
digitalWrite():这是为数字 I/O 引脚开发的。此函数将引脚设置为HIGH(5V)或LOW(0V),这些引脚已经通过pinMode()配置为OUTPUT。例如,以下代码行将数字引脚 13 设置为HIGH:digitalWrite(13, HIGH); -
digitalRead():与digitalWrite()类似,此函数帮助你读取配置为INPUT的数字引脚的状态:value = digitalRead(13); -
analogRead():此函数从特定的模拟引脚读取值。该值在 0 和 1023 的整数值之间线性映射,以表示从 0V 到 5V 的电压:value = analogRead(0); -
analogWrite():此函数用于在数字引脚上提供模拟输出结果。该技术称为 PWM,这将在第四章深入 Python-Arduino 原型设计中解释。仍然需要注意的是,此函数并非为所有数字引脚设计,而仅适用于指定为 PWM 引脚的引脚。
语句
如果你熟悉任何其他面向对象的编程语言,你肯定在程序中广泛使用了语句。Arduino 语言使用传统的 C/C++语句,如if/else、while、switch/case和for来控制程序的流程。现在我们不深入探讨这些语句,它们将在书中通过实际例子进行描述。
摘要
好的!你已经成功完成了相对平凡的安装和配置 Python 以及 Arduino IDE 的任务。无论你的系统是 Mac OS X、Linux 还是 Windows 系统,现在都已准备好迎接接下来的章节。在本章中,我们了解了 Arduino 的历史和构建模块。我们还学习了 Python 编程和 Arduino 语言的基础。现在,你准备好动手操作真实硬件,开始探索计算机与硬件的接口。在下一章中,我们将介绍接口的第一步,即使用串行接口将 Arduino 连接到计算机。
第二章:使用 Firmata 协议和 pySerial 库
在前一章中,你学习了 Python 编程语言和 Arduino 硬件平台的基础知识,以便开始使用。如果你直接阅读本章而没有阅读前一章,我们假设你对这些技术有一定的专业知识或工作经验。本章描述了两个将 Arduino 与 Python 连接所需的重要组件:
-
Arduino Firmata 协议
-
Python 的串行库名为
pySerial
尽管 Firmata 协议对于将 Arduino 与 Python 接口很有用,但它也可以作为一个独立的工具来开发各种应用。
是时候拿出你的 Arduino 硬件并开始动手实践了。在本章的过程中,你需要一个 LED 灯、一个面包板、一个 1 千欧姆电阻以及你已经在上一章中使用过的组件,即 Arduino Uno 和 USB 线。
注意
如果你使用的是任何其他版本的 Arduino,你可以从arduino.cc/en/Guide/HomePage或位于forum.arduino.cc/的社区支持的 Arduino 论坛中获取更多信息。
连接 Arduino 板
如前一章所述,本书支持所有主要操作系统,本节将为你提供连接和配置 Arduino 板的步骤。在前一章中,我们使用了示例代码来开始使用 Arduino IDE。如果你没有按照前一章提供的信息成功与 Arduino 通信,请遵循本节提供的说明,在计算机和 Arduino 之间建立连接。首先,使用 USB 线将 Arduino 板连接到计算机的 USB 端口,并按照你的操作系统步骤进行操作。
Linux
如果你使用的是最新的 Ubuntu Linux 版本,一旦你连接 Arduino 板并打开 Arduino IDE,系统会要求你将自己的用户名添加到 dailout 组,如下面的截图所示。点击添加按钮并从系统中注销。你不需要重启计算机,更改即可生效。使用相同的用户名登录并打开 Arduino IDE。

如果你没有看到这个对话框,请检查是否可以在 Arduino IDE 的工具菜单中看到串行端口选项。可能是因为安装了其他程序已经将你的用户名添加到了 dailout 组。如果你没有对话框,且在串行端口没有选择项,请在终端中执行以下脚本,其中<username>是你的 Linux 用户名:
$ sudo usermod -a -G dialout <username>
此脚本将把您的用户名添加到拨出组,并且它也应该适用于其他 Linux 版本。在 Linux 中,Arduino 板通常连接为/dev/ttyACMx,其中x是整数值,取决于您的物理端口地址。如果您使用的是除 Ubuntu 以外的任何其他 Linux 发行版,您可能需要从 Arduino 网站上的 Linux 安装页面(playground.arduino.cc/Learning/Linux)检查与 Arduino 串行端口关联的正确组。
注意
对于 Fedora Linux 发行版,将uucp和lock组与dialout组合并,以控制串行端口:
$ sudo usermod -a -G uucp,dialout,lock <username>
Mac OS X
在 Mac OS X 中,当您通过串行端口连接 Arduino 时,操作系统将其配置为网络接口。在 OS X Mavericks 中,一旦 Arduino 板连接,从系统偏好设置中打开网络。应该会出现一个对话框,表明检测到新的网络接口。点击Thunderbolt Bridge的确定,然后点击应用。以下截图显示了添加新网络接口的对话框:

对于 OS X Lion 或更高版本,连接 Arduino 板时,将出现一个对话框,要求您添加新的网络接口。在这种情况下,您不需要导航到您的网络首选项。如果您看到状态为未连接并以红色突出显示的网络接口,请不要担心,因为它应该可以正常工作。
打开 Arduino IDE,从工具菜单导航到串行端口。您应该能看到类似于以下截图中的选项。Arduino 板连接的串行端口可能会根据您的 OS X 版本和连接的物理端口而有所不同。确保您为 USB 调制解调器选择一个tty接口。如以下截图所示,Arduino 板连接到串行端口/dev/tty.usbmodemfd121:

Windows
如果您使用的是 Windows,Arduino 串行端口的配置非常简单。当您第一次连接 Arduino 板时,操作系统将自动安装必要的驱动程序。一旦此过程完成,从菜单栏中的串行端口选项中选择一个合适的 COM 端口。从主菜单中,导航到工具 | 串行端口并选择 COM 端口。
故障排除
即使按照前面提到的步骤操作,如果您仍然看不到以下截图所示的突出显示的串行端口选项,那么您可能遇到了问题。这可能有两个主要原因:串行端口被另一个程序使用,或者 Arduino USB 驱动程序没有正确安装。
如果有除 Arduino IDE 之外的其他程序正在使用特定的串行端口,请终止该程序并重新启动 Arduino IDE。有时在 Linux 中,brltty 库与 Arduino 串行接口冲突。请删除此库,注销并重新登录:
$ sudo apt-get remove brltty
在 Windows 中,重新安装 Arduino IDE 也有效,因为这个过程会重新安装和配置 Arduino USB 驱动程序。

小贴士
Arduino 板一次只能由一个程序使用。在尝试使用 Arduino IDE 时,确保任何之前使用的程序或其他服务都没有使用串行端口或 Arduino,这一点非常重要。当我们开始在下节使用多个程序控制 Arduino 时,这个检查将变得非常重要。
假设你现在可以在 Arduino IDE 中选择串行端口,我们可以继续编译并将草图上传到您的 Arduino 板。Arduino IDE 预装了示例草图,您可以尝试使用它们。然而,在我们开始尝试复杂的示例之前,让我们先浏览下一节,该节解释了 Firmata 协议,并指导您逐步编译和上传草图。
介绍 Firmata 协议
在 Arduino 之前,基于微控制器的应用程序领域仅限于硬件程序员。Arduino 使来自其他软件领域的开发者和甚至非编码社区的开发者能够轻松地开发基于微控制器的硬件应用程序。Arduino 由一个简单的硬件设计组成,包括微控制器和 I/O 引脚,用于连接外部设备。如果能够编写一个 Arduino 草图,可以将微控制器和这些引脚的控制权转移到外部软件机制,那么这将减少每次修改上传 Arduino 草图的努力。这个过程可以通过开发这样的 Arduino 程序来完成,然后可以通过串行端口对其进行控制。存在一个名为 Firmata 的协议,它正是这样做的。
什么是 Firmata?
Firmata 是一种通用协议,允许微控制器与在计算机上托管的应用软件之间进行通信。任何能够进行串行通信的计算机主机上的软件都可以使用 Firmata 与微控制器通信。Firmata 使 Arduino 直接对软件提供完全访问权限,并消除了修改和上传 Arduino 草图的流程。
要使用 Firmata 协议,开发者可以一次性将支持该协议的草图上传到 Arduino 客户端。之后,开发者可以在主机计算机上编写自定义软件并执行复杂任务。该软件将通过串行端口向配备 Firmata 的 Arduino 板提供命令。他们可以在不中断 Arduino 硬件的情况下,不断更改主机计算机上的逻辑。
编写自定义 Arduino 草图的做法对于 Arduino 板需要本地执行任务的独立应用程序仍然有效。我们将在接下来的章节中探讨这两种选项。
注意
您可以从官方网站www.firmata.org了解更多关于 Firmata 协议及其最新版本的信息。
将 Firmata 草图上传到 Arduino 板
开始测试 Firmata 协议的最佳方式是将标准 Firmata 程序上传到 Arduino 板,并使用主机上的测试软件。在本节中,我们将演示一种将具有此标准 Firmata 程序的自定义 Arduino 草图上传到板的方法。这将成为将来上传任何草图时的默认方法。
实现 Firmata 协议需要最新的 Firmata 固件版本,您无需担心编写它。最新的 Arduino IDE 自带标准版本的 Firmata 固件,我们建议您使用最新的 IDE 以避免任何冲突。现在,按照以下步骤将程序上传到您的 Arduino 板:
-
如以下截图所示,通过在 Arduino IDE 中导航到文件 | 示例 | Firmata | StandardFirmata来打开StandardFirmata草图:
![将 Firmata 草图上传到 Arduino 板]()
-
此操作将在新窗口中打开另一个草图簿,并在编辑器中加载StandardFirmata草图。不要修改草图中的任何内容,并继续执行下一节中描述的编译过程。重要的是不要修改代码,因为我们将要使用的测试软件与最新的未更改固件兼容。
-
一旦打开StandardFirmata草图,下一步就是为您的 Arduino 板编译它。在上一节中,我们已经将 Arduino 板连接到计算机并选择了正确的串行端口。然而,如果新的草图簿与之前的配置不同,请按照上一节的步骤操作,即选择适当的串行端口和 Arduino 板类型。
-
要编译当前草图,请点击以下截图所示的验证图标。您也可以通过导航到草图 | 验证/编译或点击Ctrl + R(如果您使用的是 Mac OS X,则为command + R)来编译它:
![将 Firmata 草图上传到 Arduino 板]()
编译过程应该没有错误完成,因为我们使用的是 IDE 自带的默认示例代码。现在,是时候将草图上传到板上了。请确保您已经连接了板。
-
按照以下截图所示,在工具栏中按下上传图标。此操作将上传编译后的代码到您的 Arduino 板:
![将 Firmata 草图上传到 Arduino 板]()
完成后,你应该在 IDE 中看到上传完成的文本,如图下所示:

你的 Arduino 板现在已安装了最新的 Firmata 固件,并等待来自计算机的请求。让我们继续到下一节,开始测试 Firmata 协议。
测试 Firmata 协议
在上一章中,我们使用了 13 号引脚上的板载 LED 来测试闪烁程序。这次,我们将使用外部 LED,帮助你开始使用 Arduino 板组装硬件组件。由于所有即将到来的练习和项目都将要求你使用面包板将硬件组件如传感器和执行器连接到 Arduino 板上,我们希望你开始获得实际操作这些组件的实践经验。
现在是时候使用我们在本章开头要求你获取的 LED 了。在我们开始接线 LED 之前,让我们首先了解它的物理原理。你获得的 LED 应该有两个引脚:一个短的和一个长的。短的引脚连接到 LED 的阴极,并且需要通过一个电阻连接到地。如图所示,我们使用一个 1 千欧姆的电阻将 LED 的阴极接地。连接到阳极的长引脚需要连接到 Arduino 板上的一个数字引脚。
如下图所示,我们将阳极连接到了数字引脚 13。查看图示并按照显示的接线方式接线。确保你已将 Arduino 板从主机计算机断开,以避免静电造成的任何损坏。

在这个例子中,我们将使用 LED 来测试 Firmata 协议的一些基本功能。我们已将 Firmata 代码上传到 Arduino 板,并准备好从主机计算机控制 LED。
注意
上述接线图是使用一个名为Fritzing的开源工具创建的。我们将在下一章全面介绍 Fritzing 工具,因为它将成为我们在实际物理接线之前创建接线图的标准软件。
使用 Firmata 与主机计算机通信有多种方式,例如使用支持的库在 Python 中编写自己的程序或使用预构建的测试软件。从下一节开始,我们将编写自己的程序来使用 Firmata,但在这个阶段,让我们使用一个免费工具进行测试。官方 Firmata 网站www.firmata.org还提供了测试工具,您可以从主页上的Firmata 测试程序部分下载。该网站为不同的操作系统提供了名为firmata_test的不同工具变体。按照以下步骤,您可以测试 Firmata 协议的实现:
-
将
firmata_test程序的适当版本下载到您的计算机上。 -
现在,使用 USB 线将带有 LED 的 Arduino 板连接到主机计算机,并运行下载的
firmata_test程序。您将能够在程序成功执行后看到一个空窗口。 -
如以下屏幕截图所示,从下拉菜单中选择适当的端口。请确保选择您用于上传 Arduino 草图相同的端口。
![测试 Firmata 协议]()
小贴士
在这一点上,请确保您的 Arduino IDE 没有使用相同的端口号连接到板。如我们之前提到的,串行接口一次只授予一个应用程序独家访问权限。
-
一旦您选择了 Arduino 串行端口,程序将加载多个带有包含引脚编号标签的下拉框和按钮。您可以在下面的屏幕截图中看到,程序已加载了 12 个数字引脚(从引脚 2 到引脚 13)和 6 个模拟引脚(从引脚 14 到引脚 19)。由于我们使用 Arduino Uno 板进行我们的应用,测试程序只加载 Arduino Uno 板的部分引脚。如果您使用 Arduino Mega 或任何其他板,程序中显示的引脚数量将根据该特定 Arduino 板变体支持的引脚数量而定。
![测试 Firmata 协议]()
小贴士
在 Linux 上使用 firmata_test 程序
在 Linux 平台上,您可能需要修改下载文件的属性并使其可执行。从同一目录中,在终端中运行以下命令使其可执行:
$ chmod +x firmata_test一旦您更改了权限,请使用以下命令从终端运行程序:
$ ./firmata_test -
如你在程序窗口中所见,你还有两列以及其他包含标签的列。程序中的第二列允许你选择适当引脚的角色。你可以指定数字引脚(在 Arduino Uno 的情况下,从 2 到 13)作为输入或输出。如以下截图所示,当你选择 2 号和 3 号引脚作为输入引脚时,你会在第三列看到低。这是正确的,因为我们没有将这些引脚连接到任何输入。你可以通过更改多个引脚的角色和值来玩弄程序。
![测试 Firmata 协议]()
由于我们已经将 LED 连接到数字引脚 13,所以在你玩弄其他引脚时,我们不会期望在板上出现任何物理变化。
-
现在,选择引脚 13 作为输出引脚并按下低按钮。这将改变按钮的标签为高,并且你会看到 LED 灯被点亮。通过执行此操作,我们已经将数字引脚 13 的逻辑更改为 1,即高,这在引脚上相当于+5 伏特。这种电压足以点亮 LED。你可以通过再次点击按钮并将它切换到低来将引脚 13 的级别改回 0。这将使电压回到 0 伏特。
![测试 Firmata 协议]()
我们在这里使用的程序非常适合测试基础知识,但不能用来编写使用 Firmata 协议的复杂应用程序。在实际应用中,我们确实需要使用自定义代码来执行 Firmata 方法,这不仅包括切换 LED 状态,还包括实现智能逻辑和算法、与其他组件接口等。从下一节开始,我们将使用 Python 来处理这些应用。
开始使用 pySerial
在上一节中,你学习了 Firmata 协议。这是一个简单快捷的开始使用 Arduino 的方法。尽管 Firmata 协议可以帮助你在不修改 Arduino 草图的情况下从电脑上开发复杂的应用程序,但我们还没有准备好开始编写这些应用程序的代码。
编写这些复杂应用程序的第一步是在你的编程环境和 Arduino 之间通过串行端口提供一个接口。在这本书中,你将需要为每个我们开发的项目在 Python 解释器和 Arduino 之间建立连接。
编写自己的库,该库包括实现函数和规范以在串行协议上启用通信,是一个不方便且耗时的过程。我们将通过使用一个开源、维护良好的 Python 库pySerial来避免这种情况。
pySerial 库通过封装串行端口的访问来启用与 Arduino 的通信。此模块通过 Python 属性提供对串行端口设置的访问,并允许你通过解释器直接配置串行端口。pySerial 将成为 Python 和 Arduino 之间未来通信的桥梁。让我们从安装 pySerial 开始。
安装 pySerial
我们在第一章Python 和 Arduino 入门中安装了包管理器 Setuptools。如果你跳过了那一章并且对此不确定,那么请阅读该部分。如果你已经知道如何安装和配置 Python 库包,则可以跳过这些安装步骤。
从这个阶段开始,我们将只使用基于 pip 的安装命令,因为它们在第一章Python 和 Arduino 入门中描述的明显优势:
-
打开终端或命令提示符,并执行以下命令:
> pip install pyserialWindows 操作系统不需要管理员级别的用户访问来执行命令,但在基于 Unix 的操作系统中,你应该有 root 权限来安装 Python 包,如下所示:
$ sudo pip install pyserial如果你想要从源代码安装
pySerial库,请从pypi.python.org/pypi/pyserial下载存档,解压它,然后从pySerial目录中运行以下命令:$ sudo python setup.py install -
如果 Python 和 Setuptools 安装正确,那么在安装完成后,你应在命令行中看到以下输出:
. . Processing dependencies for pyserial Finished processing dependencies for pyserial这意味着你已经成功安装了
pySerial库,并且可以进入下一部分。 -
现在,为了检查
pySerial是否成功安装,启动你的 Python 解释器,并使用以下命令导入pySerial库:>>> import serial
玩转 pySerial 示例
你的 Arduino 板有来自上一个示例的 Firmata 草图 StandardFirmata。为了玩转 pySerial,我们不再使用 Firmata 协议。相反,我们将使用另一个简单的 Arduino 草图,该草图实现了可以在 Python 解释器上捕获的串行通信。
坚持不进行任何 Arduino 草图编码的承诺,让我们从 Arduino IDE 中选择一个示例草图:
-
如以下截图所示,导航到文件 | 示例 | 01. 基础 | DigitalReadSerial。
![玩转 pySerial 示例]()
-
使用前面描述的方法编译并上传程序到 Arduino 板。选择你的 Arduino 连接的适当串行端口,并记下它。正如你在草图中所见,这段简单的 Arduino 代码以 9600 bps 的波特率通过串行端口传输数字引脚 2 的状态。
-
在不将 Arduino 板从计算机断开连接的情况下,打开 Python 解释器。然后,在 Python 解释器上执行以下命令。确保将
/dev/ttyACM0替换为你之前记下的端口名称:>>> import serial >>> s = serial.Serial('/dev/ttyACM0',9600) >>> while True: print s.readline() -
执行时,你应在 Python 解释器中看到重复的
0值。按Ctrl + C来终止此代码。正如你所见,由于草图中的循环函数,Arduino 代码会持续发送消息。我们没有将任何东西连接到引脚 2,因此我们得到了状态0,即低。 -
如果你清楚自己在做什么,你可以将任何数字传感器连接到引脚 2,然后再次运行脚本以查看更改后的状态。
在前面的 Python 脚本中,serial.Serial方法用于接口和打开指定的串行端口,而readline()方法则从该接口读取每行,以\n结束,即换行符。
注意
换行符是一个特殊字符,表示文本行的结束。它也被称为行结束(EOL)或换行+回车(LF + CR)。了解更多关于换行符的信息,请访问en.wikipedia.org/wiki/Newline。
连接 pySerial 和 Firmata
在 Firmata 部分,我们已经了解到使用 Firmata 协议而不是不断修改 Arduino 草图并上传它对于简单程序是多么有用。pySerial是一个简单的库,它通过串行端口在 Arduino 和 Python 之间提供桥梁,但它不支持 Firmata 协议。如前所述,Python 的最大好处可以用一句话来描述,“有库就能做到。”因此,存在一个名为pyFirmata的 Python 库,它是基于pySerial构建的,以支持 Firmata 协议。还有一些其他 Python 库也支持 Firmata,但我们将只在本章中关注pyFirmata。我们将在即将到来的各种项目中广泛使用这个库:
-
让我们从像安装其他 Python 包一样安装
pyFirmata开始,使用 Setuptools:$ sudo pin install pyfirmata在前面的章节中,当我们测试
pySerial时,我们将DigitalSerialRead草图上传到了 Arduino 板。 -
要使用 Firmata 协议进行通信,你需要再次上传StandardFirmata草图,就像我们在上传 Firmata 草图到 Arduino 板部分所做的那样。
-
一旦上传了这个草图,打开 Python 解释器并执行以下脚本。此脚本将
pyfirmata库导入到解释器中。它还定义了引脚号和端口。>>> import pyfirmata >>> pin= 13 >>> port = '/dev/ttyACM0' -
在此之后,我们需要将端口与微控制器板类型关联:
>>> board = pyfirmata.Arduino(port)在执行前面的脚本时,Arduino 上的两个 LED 会闪烁,因为 Python 解释器和板之间的通信链路正在建立。在测试 Firmata 协议部分,我们使用了一个预构建的程序来开关 LED。一旦 Arduino 板与 Python 解释器关联,这些功能可以直接从提示符中执行。
-
现在,您可以开始使用 Arduino 引脚进行实验了。通过执行以下命令来打开 LED:
>>> board.digital[pin].write(1) -
您可以通过执行以下命令来关闭 LED。在这两个命令中,我们通过传递值
1(高)或0(低)来设置数字引脚 13 的状态:>>> board.digital[pin].write(0) -
同样,您也可以从提示符中读取引脚的状态:
>>> board.digital[pin].read()
如果我们将这个脚本与具有.py扩展名的可执行文件结合起来,我们就可以拥有一个可以直接运行的 Python 程序来控制 LED,而不是在终端上运行这些单个脚本。稍后,这个程序可以扩展以执行更复杂的功能,而无需编写或更改 Arduino 草图。
注意
虽然我们在 Python 提示符中运行单个脚本,但我们将在下一章中介绍创建 Python 可执行文件的过程。
摘要
通过引入 Firmata 库,我们避免了在本章中编写任何自定义 Arduino 草图。我们将在本书剩余部分继续这种做法,并且只有在需要时才会使用或创建自定义草图。在本章中,您通过与 Arduino 板交互使 LED 闪烁来与之交互,这是开始硬件项目的最简单方法。现在,是时候开始您的第一个项目了,我们也将使更多的 LED 闪烁。有人可能会问,如果我们已经做到了这一点,那么为什么还需要另一个项目来使 LED 闪烁呢?让我们来看看。
第三章。第一个项目 – 运动触发 LED
在上一章中,你学习了 Python-Arduino 接口的基础知识。我们通过一些练习来提供使用有用的 Arduino 协议 Firmata 和 Python 库的动手经验。现在,是时候进行你的第一个“Python + Arduino”项目了。
我们将从这个章节开始讨论项目目标和设计项目软件流程和硬件布局所需的组件。就像任何其他基于微控制器的硬件项目一样,你可以在 Arduino 上使用代码并实现项目的整个逻辑。然而,本书的目标是帮助你以这种方式利用 Python,从而简化并扩展你的硬件项目。尽管在接下来的章节中我们将使用 Python 程序辅助 Arduino 草图的双向方法,但我们希望让你熟悉这两种编程方式。由于这是你第一次构建硬件项目,本章为你提供了两种不同的编程方法:仅使用 Arduino 草图和使用带有 Firmata 协议的 Python 程序。包含 Arduino 草图的方法是为了让你获得与 Arduino 组件(如 I/O 引脚和串行通信)的完整体验。
运动触发 LED – 项目描述
当你开始学习任何编程语言时,在大多数情况下,你将编写代码来打印“Hello World!”。同时,在硬件项目中,大多数教程都是从帮助用户编写代码来闪烁 LED 开始的。这些练习或项目对于开发者开始使用语言是有用的,但大多数情况下,它们对现实世界的应用并没有任何重要性。然而,我们不想让你被一个复杂且复杂的项目压倒,这个项目可能需要你具备相当多的领域知识。
在上一章使用 Firmata 协议工作时,我们已经在 Arduino 板上闪烁了一个 LED。为了保持传统(拥有闪烁的 LED 作为第一个主要项目)并激发对项目的兴趣,让我们在闪烁 LED 项目中加入一些变化。在这个项目中,我们将闪烁两个不同的 LED,但不是以随机的方式执行这些动作,而是通过使用运动传感器测量的事件来执行。尽管由于这是你的第一个项目,项目的难度级别很简单,但它具有现实世界的应用价值,并且可以作为你日常生活中的简单应用。
项目目标
项目目标可以用一句话描述如下:“当检测到任何运动时,使用红色 LED 发出警报,并使用绿色 LED 显示正常状态。”在详细的目标列表中,你必须完成以下任务以满足提到的项目目标:
-
使用被动红外(PIR)传感器将环境中的任何运动检测为一个事件
-
使用红色 LED 执行闪烁动作以表示此事件
-
否则,使用绿色 LED 执行闪烁动作
-
在执行动作后,保持系统循环并等待下一个事件
该项目可以作为 DIY 应用或作为其他项目的一部分进行实施,只需进行少量修改。以下是一些可以应用本项目概念的一些示例:
-
作为 DIY 安全系统,用于监控房间内的运动(
www.instructables.com/id/PIR-Sensor-Security/) -
在智能家居应用中,如果没有人,它可以自动关闭灯光(
www.instructables.com/id/Arduino-Home-Monitor-System/) -
在有额外硬件组件和适当代码的支持下,可用于自动车库门开启应用
-
在 DIY 野生动物记录项目中,当检测到任何运动时,可以使用它触发相机而不是 LED(
www.instructables.com/id/Motion-triggered-camera/)
组件列表
在上一章中,我们仅使用 Arduino、Arduino USB 线和计算机进行编程。本项目所需的主要硬件组件是 PIR 运动传感器。您还需要额外的 LED。我们建议您使用与您已有的 LED 不同的颜色。所需组件的描述如下:
-
PIR 传感器:这些传感器被广泛用于 DIY 项目中的运动检测。它们体积小、价格低廉、功耗低,并且与 Arduino 等硬件平台兼容。PIR 传感器使用一对热释电传感器来检测红外辐射。如果没有运动,这些传感器的输出会相互抵消。任何环境中的运动都会通过这些热释电传感器产生不同水平的光辐射,差异将触发一个输出为
HIGH(+5 伏特)的信号。我们将使用 SparkFun 销售的 PIR 传感器,您可以从www.sparkfun.com/products/8630获取它。PIR 传感器配备了所需的印刷电路板(PCB)。其范围为 20 英尺(6 米),对于该项目来说足够了。以下图片显示了 SparkFun 网站上可用的 PIR 传感器:![组件列表]()
来源:Sparkfun Inc.
-
LED 灯:我们建议您在项目中使用绿色和红色 LED 灯。如果它们不可用,您可以使用任何两种不同颜色的 LED 灯。
-
电线、电阻和面包板:您需要一些电线和一个面包板来完成连接。作为最佳实践,至少要有三种不同颜色的电线连接器来表示电源、地线和信号。您还需要两个 220 欧姆和一个 10 千欧姆的拉电阻。
-
Arduino 板:Arduino Uno 板对于项目需求来说是足够的。你也可以使用 Arduino Mega 或任何其他 Arduino 板来完成这个项目。该项目只需要三个 I/O 引脚,而任何可用的 Arduino 板都配备了超过三个 I/O 引脚。
-
USB 线:你需要 USB 线来上传 Arduino 代码并与 Arduino 板进行串行通信。
-
计算机:我们在前面的章节中已经为你的操作系统配置了一台装有 Python 和 Arduino IDE 的计算机。你将需要这台计算机来完成项目。确保你已经安装并配置了我们在前面的章节中安装和配置的所有软件组件。
软件流程设计
在开始任何硬件系统的工作之前,第一步是使用逻辑设计项目流程。我们建议你将项目绘制成流程图,以便更好地理解组件布局和代码流程。以下图显示了项目的流程,你可以看到一旦检测到运动,项目就会在循环中运行,并执行相应的 LED 动作:

正如你所见,程序逻辑首先检测 PIR 传感器的状态,并根据该状态执行相应的动作。使用单个 Arduino 指令,你只能打开或关闭 LED。为了执行闪烁操作,我们需要在动作之间加入时间延迟,重复打开和关闭动作。我们还将插入延迟在每个连续循环的执行之间,以便 PIR 传感器的输出可以稳定下来。请注意,我们将使用相同的流程来编写两种编程方法的代码。
硬件系统设计
设计软件流程图有助于你编写程序,并协助你识别项目中的动作和事件。硬件系统设计的过程包括电路连接、原理图设计、仿真、验证和测试。这个设计过程提供了对项目及其硬件组件的详细了解。它还有助于对项目架构进行初步的验证和测试。在我们跳到这个项目的硬件设计过程之前,让我们先熟悉一下这些有用的工具。
介绍 Fritzing – 一款硬件原型设计软件
你不需要为这个项目设计硬件系统。总的来说,在这本书中,硬件系统设计将会提供,因为本书的主要焦点是编程而不是硬件设计。
如果你对手动设计或硬件组件的快速原型设计感兴趣,用于此目的的开源软件工具被称为Fritzing。你可以使用 Fritzing 来设计项目的原理图,并且可以从fritzing.org/download/获取。
Fritzing 是一个由设计师、艺术家和爱好者支持的社区支持的电子设计自动化软件项目。它允许您将您的硬件草图从纸上转换为软件电路图。Fritzing 还为您提供了一个从设计创建 PCB 布局的工具。Fritzing 广泛支持 Arduino 和其他流行的开源 DIY 硬件平台。您可以通过内置的示例项目探索 Fritzing。
安装并运行 Fritzing。以下截图显示了打开 Fritzing 后显示的默认项目之一:

如您所见,一个包含虚拟硬件组件的工具箱位于打开窗口的右侧。位于中心的主体编辑空间允许用户从工具箱中拖放组件,并允许用户完成这些组件之间的连接。您可以在fritzing.org/learning/了解更多 Fritzing 提供的功能,并查看一些动手教程。
使用面包板
一旦您熟悉了 Fritzing,您就有灵活性来创建自己的电路,或者您始终可以使用书中提供的 Fritzing 文件。然而,还有一个挑战,那就是将您的虚拟电路移植到物理电路。电子项目中使用的最基本组件之一是允许您实现连接并构建物理电路的面包板。
面包板内含有智能组织的金属排,这些金属排隐藏在带有塑料孔的组件下面。这个组件帮助用户在不进行任何焊接工作的前提下连接电线。通过这些孔插入和移除电线或电子组件非常容易。以下图显示了带有几个组件和一些电线连接的小型面包板:

注意
在learn.sparkfun.com/tutorials/how-to-use-a-breadboard了解更多关于面包板和使用它们的教程。
面包板主要有两种类型的连接条:端子条和电源轨。如图所示,端子条是电学上短接的孔的垂直列。简单来说,一旦你将任何组件连接到端子条中的一列,该组件就会与该列中的每个孔电学连接。端子条的列之间由双列直插式封装(DIP)支撑间隙分隔。(DIP 是电子组件的常见封装。)在同一列中,DIP 支撑间隙上方和下方的端子条在电学上是独立的。同时,电源轨在整个面包板的一行中水平短接。电源轨主要用于连接电源的正极和地,因此它可以轻松地分配到所有组件。
注意
面包板的历史
在电子的早期年代,人们使用真正的面包板(用于切面包的)来用钉子和电线连接他们的大型组件。随着电子组件开始变得更小,组装电路的板也变得更好。这个术语在这次演变中保留了下来,我们仍然称现代的板为面包板。如果你感兴趣,可以查看www.instructables.com/id/Use-a-real-Bread-Board-for-prototyping-your-circui/,它提供了使用原始面包板组装电路的说明。
设计硬件原型
是时候收集前面提到的硬件组件并开始构建系统了。下一图显示了使用 Fritzing 开发的项目的电路图。如果你有电路组装的先前经验,请继续按照图中的显示连接组件:

如果这是你第一次使用传感器和面包板,请按照以下步骤完成电路组装:
-
将 Arduino 的 VCC(+5V)和地连接到面包板。
-
将红色 LED 的正极(长引脚)连接到 Arduino 板的数字引脚 12。将红色 LED 的负极(短引脚)通过 220 欧姆电阻器连接到地。
-
将绿色 LED 的正极(长引脚)连接到 Arduino 板的数字引脚 13。将绿色 LED 的负极(短引脚)通过 220 欧姆电阻器连接到地。
-
将 PIR 传感器的 VDD 连接到面包板上的 VCC。使用相同的线色来表示相同的连接类别。这将极大地帮助电路的故障排除。
-
将 PIR 传感器的信号(中间引脚)通过 10 千欧姆的上拉电阻器连接到 Arduino 板的数字引脚 7。
大多数专家更喜欢原理图而不是我们之前使用的原型图。当你使用与原型图中的确切组件兼容的组件时,原理图非常有用。以下是我们之前设计的电子电路的原理图。此图也是使用 Fritzing 获得的:

你的系统现在已准备好运行 Arduino 程序。由于我们将使用相同的硬件进行编程方法,除非你遇到问题,否则你几乎完成了电子工作。为了确保一切连接得完美,让我们在下一节检查这些连接。
注意
注意,上拉电阻被用来确保 PIR 传感器的输出信号达到预期的逻辑电平。
测试硬件连接
一旦电路连接完成,你就可以直接进入编程部分。作为一个最佳实践,我们建议你验证电路连接并检查传感器的状态。我们假设你的 Arduino 板已经配备了我们在上一章中讨论的StandardFirmata草图。否则,请参考上一章,并将StandardFirmata草图上传到你的 Arduino 板。
验证我们的电路实现的最佳方式是使用我们在上一章中使用的 Firmata 测试程序。根据项目设置,PIR 传感器向 Arduino 引脚 7 提供事件输入。在测试程序中,将引脚 7 的类型更改为输入,并在传感器上方挥动手,你应该能够看到引脚的状态为高,如以下截图所示:

通过将引脚 12 和 13 设置为输出引脚并切换按钮来设置引脚的状态,检查 LED 的连接。如果你在切换按钮时看到 LED 闪烁,那么你的连接工作得非常完美。
如果你无法成功执行这些检查,请验证并重复设计步骤。
方法 1 – 使用独立的 Arduino 草图
正如我们在前面的章节中讨论的,一个项目可以通过创建特定于项目的原生 Arduino 代码或使用 Python-Arduino 混合方法来实现。
原生的 Arduino 草图在不需要与计算机系统进行通信或通信可忽略的情况下非常有用。尽管这种独立项目在没有串行连接的情况下可以持续运行,但更新和上传 Arduino 草图进行微小修改是困难的。
如果你查看这个项目的各种应用,你会注意到其中只有少数需要将项目实现为一个仅检测运动并闪烁 LED 的独立系统。这种类型的系统可以通过一个简单的 Arduino 草图轻松实现。
项目设置
在我们继续项目之前,请确保您已准备好以下事项:
-
硬件组件已设置并正常运行
-
您的 Arduino 通过 USB 线连接到计算机
-
您的计算机上安装了 Arduino IDE,您可以通过 IDE 访问连接的 Arduino 板
Arduino 草图
本节描述了项目的 Arduino 代码。在我们逐步描述代码之前,让我们首先遵循以下步骤来运行项目:
-
打开 Arduino IDE。
-
从文件菜单中打开一个新的草图簿。
-
将以下 Arduino 代码复制到草图并保存:
int pirPin = 7; //Pin number for PIR sensor int redLedPin = 12; //Pin number for Red LED int greenLedPin = 13; //Pin number for Green LED void setup(){ Serial.begin(9600); pinMode(pirPin, INPUT); pinMode(redLedPin, OUTPUT); pinMode(greenLedPin, OUTPUT); } void loop(){ int pirVal = digitalRead(pirPin); if(pirVal == LOW){ //was motion detected blinkLED(greenLedPin, "No motion detected."); } else { blinkLED(redLedPin, "Motion detected."); } } // Function which blinks LED at specified pin number void blinkLED(int pin, String message){ digitalWrite(pin,HIGH); Serial.println(message); delay(1000); digitalWrite(pin,LOW); delay(2000); } -
编译并将草图上传到 Arduino 板。
现在,您已经使用第一种编程方法完成了项目,并且已成功将其部署到硬件上。它应该正在运行设计的算法以检测运动事件并执行闪烁动作。
由于您的项目运行正常,现在是时候理解代码了。像任何其他 Arduino 程序一样,代码有两个强制性的函数:setup()和loop()。它还有一个自定义函数blinkLED(),用于执行稍后解释的特定动作。
setup()函数
如您在前面的代码片段中所见,我们在程序开始时将变量分配给了 Arduino 引脚。在setup()函数中,我们配置了这些变量,使其定义为输入或输出引脚:
pinMode(pirPin, INPUT);
pinMode(redLedPin, OUTPUT);
pinMode(greenLedPin, OUTPUT);
在这里,pirPin、redLedPin和greenLedPin分别是数字引脚 7、12 和 13。在同一个函数中,我们还配置了 Arduino 板以在 9600 bps 的波特率下提供串行连接性:
Serial.begin(9600);
loop()函数
在loop()函数中,我们反复监控来自pirPin数字引脚的输入以检测运动。当检测到运动时,该引脚的输出为HIGH,否则为LOW。此逻辑通过简单的if-else语句实现。当满足此条件时,函数调用用户定义的函数blinkLED(),以对 LED 执行适当的动作。
用户定义的函数是任何编程语言的一个重要方面。让我们花些时间学习如何创建自己的 Arduino 函数以执行各种动作。
使用自定义 Arduino 函数进行工作
当一段代码需要重复执行以执行相同动作时,会使用函数。用户可以创建自定义函数来组织代码或执行重复动作。为了成功使用自定义函数,用户需要从强制性的 Arduino 函数(如loop()、setup()或任何导致这些强制性函数的其他函数)中调用它们:
return-type function_name (parameters){
# Action to be performed
Action_1;
Action_2;
Return expression;
}
在先前的 Arduino 函数框架中,return-type可以是任何 Arduino 数据类型,如int、float、string等,或者如果代码不返回任何内容,则为void。以下是我们项目代码中使用的自定义函数:
void blinkLED(int pin, String message){
digitalWrite(pin,HIGH);
Serial.println(message);
delay(1000);
digitalWrite(pin,LOW);
delay(2000);
}
在我们的项目中,当从loop()函数调用blinkLED()函数时,它不会返回任何值。因此,return-type是void。在调用函数时,我们传递引脚号和消息作为参数:
blinkLED(greenLedPin, "No motion detected.");
这些参数随后被blinkLED()函数用于执行操作(在串行端口上写入消息并设置 LED 状态)。此函数还通过使用delay()函数引入延迟来执行闪烁动作。
测试
我们在测试硬件连接部分使用手动输入通过 Firmata 测试程序验证了设计的系统。由于我们现在已经实现了软件设计,我们需要验证项目是否能够自主且重复地执行客观任务。
将 USB 端口连接到计算机后,通过导航到工具 | 串行监视器或按Ctrl + Shift + M打开 Arduino IDE 中的串行监视工具。你应该开始在串行监视器窗口看到类似于以下截图的消息:

当编写blinkLED()函数以执行操作时,我们包括了一个通过串行端口写入字符串的动作。将你的手在 PIR 传感器上方移动,以便 PIR 传感器可以检测到运动。此事件应触发系统闪烁红色 LED 并在串行监视器上显示字符串Motion detected。一旦你保持稳定并避免任何运动一段时间,你将能够看到绿色 LED 闪烁,直到通过 PIR 传感器检测到下一次运动。
故障排除
故障排除是一个重要的过程,如果出现问题,以下是一些示例问题和相应的故障排除步骤:
-
串行输出正确,但没有闪烁的 LED:
- 检查面包板上的 LED 连接
-
LED 闪烁,但没有串行输出:
-
检查串行监视器配置的端口
-
检查串行监视器中的波特率是否正确(9600 bps)
-
-
没有串行输出和没有闪烁的 LED:
-
检查 PIR 传感器连接并确保你从 PIR 传感器获得信号
-
检查你的 Arduino 代码
-
检查电源和地线连接
-
方法 2 – 使用 Python 和 Firmata
在上一章中,我们讨论了使用由 Firmata 辅助的 Python 编程相对于使用原生 Arduino 脚本的优点。基于 Python 的编程方法在进行任何算法或参数更改时提供了切实可行的体验。在本节中,我们将探讨这些优点,并学习重要的 Python 编程范式。
项目设置
在我们继续进行 Python 编程之前,让我们确保你已经完成了以下操作:
-
确保硬件组件已按照系统设计描述设置好
-
使用 USB 线将 Arduino 连接到你的计算机
-
将StandardFirmata草图重新上传到 Arduino
-
确保您的计算机上已安装 Python 和 Python 包(
pySerial和pyFirmata) -
获取一个文本编辑器来编写 Python 代码
使用 Python 可执行文件
在前面的章节中,我们使用交互式 Python 解释器探索了 Python 编程。然而,当处理大型项目时,很难继续使用 Python 交互式解释器进行重复性任务。与其他编程语言一样,首选的方法是创建 Python 可执行文件并在终端中运行它们。
Python 可执行文件带有 .py 扩展名,格式为纯文本。任何文本编辑器都可以用来创建这些文件。常用的编辑器包括 Notepad++、nano、vi 等。此列表还包括与 Python 安装文件一起提供的默认编辑器 IDLE。您可以使用您选择的编辑器,但请确保将文件保存为 .py 扩展名。让我们将以下代码行复制到一个新文件中,并将其保存为 test.py:
#!/usr/bin/python
a = "Python"
b = "Programming"
print a + " "+ b
要运行此文件,请在保存 test.py 文件的终端上执行以下命令:
$ python test.py
您应该在终端上看到打印的文本 Python Programming。如您所见,文件以 #!/usr/bin/python 开头,这是默认的 Python 安装位置。通过在您的 Python 代码中添加此行,您可以直接从终端执行 Python 文件。在基于 Unix 的操作系统中,您需要通过以下命令使 test.py 文件可执行:
$ chmod +x test.py
现在,由于您的文件是可执行的,您可以直接使用以下命令运行该文件:
$./test.py
注意
对于基于 Unix 的操作系统,提供 Python 解释器位置的另一种方法是使用以下代码行,而不是我们之前使用的代码行:
#!/usr/bin/env python
在 Windows 操作系统中,Python 文件由于 .py 扩展名而自动成为可执行文件。您只需双击并打开程序文件即可运行程序。
Python 代码
如您现在所知,如何创建和运行 Python 代码,让我们创建一个新的 Python 文件,并使用以下代码片段运行它。请确保根据前一章所述,根据您的操作系统更改 port 变量的值:
#!/usr/bin/python
# Import required libraries
import pyfirmata
from time import sleep
# Define custom function to perform Blink action
def blinkLED(pin, message):
print message
board.digital[pin].write(1)
sleep(1)
board.digital[pin].write(0)
sleep(1)
# Associate port and board with pyFirmata
port = '/dev/ttyACM0'
board = pyfirmata.Arduino(port)
# Use iterator thread to avoid buffer overflow
it = pyfirmata.util.Iterator(board)
it.start()
# Define pins
pirPin = board.get_pin('d:7:i')
redPin = 12
greenPin = 13
# Check for PIR sensor input
while True:
# Ignore case when receiving None value from pin
value = pirPin.read()
while value is None:
pass
if value is True:
# Perform Blink using custom function
blinkLED(redPin, "Motion Detected")
else:
# Perform Blink using custom function
blinkLED(greenPin, "No motion Detected")
# Release the board
board.exit()
您已成功使用 Python 创建并执行了您的第一个 Arduino 项目。此代码中有两个主要的编程组件:pyFirmata 方法以及执行闪烁动作的 Python 函数。程序会重复检测运动事件并执行闪烁动作。在前一节中,我们通过使用默认的 Arduino 函数 loop() 解决了这个问题。在此方法中,我们实现了 while 语句以使程序在代码被用户手动终止前保持循环。您可以使用键盘组合 Ctrl + C 终止代码。
使用 pyFirmata 方法
作为使用 Arduino 板和 Firmata 协议的一部分,你必须首先将 Arduino 板初始化为变量。允许用户将板分配给 Python 变量的 pyFirmata 方法如下:
board = pyfirmata.Arduino(port)
一旦变量的值被分配,你可以执行各种操作,例如使用该变量读取引脚或向引脚发送信号。要分配引脚的角色,使用 get_pin() 方法。在以下代码行中,d 代表数字引脚,7 是引脚号,i 代表该引脚类型是输入引脚:
pirPin = board.get_pin('d:7:i')
一旦将引脚及其角色分配给变量,该变量就可以用来读取或写入引脚上的值:
Value = pirPin.read()
可以直接将数据写入特定的引脚,如下面的代码所示:
board.digital[pin].write(1)
在这里,write(1) 方法向引脚发送 HIGH 信号。我们将在接下来的章节中学习更多的 pyFirmata 方法。
使用 Python 函数
Python 函数以 def 关键字开头,后跟函数名和输入参数或参数。函数定义以冒号(:)结束,之后进行缩进。return 语句终止函数。它还将表达式传递到函数被调用的位置。如果没有表达式,return 语句被认为是传递返回值 None:
def function_name(parameters):
action_1
action_2
return [expression]
之前提到的框架可以用来创建自定义函数以执行重复任务。在我们的项目中,我们有 blinkLED(pin, message) 函数来执行闪烁 LED 操作。此函数向指定的数字引脚发送 1 (HIGH) 和 0 (LOW) 值,同时在终端打印 message。它还引入延迟来模拟闪烁动作:
def blinkLED(pin, message):
print message
board.digital[pin].write(1)
sleep(1)
board.digital[pin].write(0)
sleep(1)
测试
你可以在终端上运行 Python 代码后立即开始测试项目。如果一切按设计进行,你应该能在终端看到以下输出:

当 PIR 传感器检测到任何运动时,你应该能在终端上看到 Motion Detected 字符串。如果你在输出中找到任何异常行为,请检查 Python 代码。
使用 Python 的好处是,如更改闪烁速度或交换 LED 角色等小修改,只需更改 Python 代码即可完成,无需处理 Arduino 或电路。
故障排除
当你运行项目时,你可能需要解决以下可能的问题:
-
串行输出正确,但没有闪烁的 LED:
- 检查面包板上的 LED 连接
-
LED 闪烁,但没有串行输出:
- 检查你是否已成功将标准 Firmata 草图安装到板上
-
如果没有串行输出和闪烁的 LED:
-
检查是否有除 Python 之外的其他程序正在使用串行端口。关闭可能正在使用该串行端口的任何程序,包括 Arduino IDE。
-
验证所有电路连接。
-
确保在 Python 代码中指定的端口号是正确的。
-
摘要
在本章中你学习到的两种编程方法中,仅使用 Arduino 脚本的这种方法代表了传统的微控制器编程范式。虽然这种方法实现起来简单,但它缺乏通过 Python-Arduino 接口获得的广泛性。尽管从现在开始的所有项目都将使用广泛的 Arduino 编程,但练习和项目将以 Python-Arduino 接口作为主要的编程方式。
从下一章开始,我们将探索可以扩展基于 Arduino 硬件项目可用性的 Python 编程的额外方面,同时将编程难度保持在最低。我们将从 Python-Arduino 原型设计开始,然后创建用于用户交互的图形界面,在完成第二个项目,该项目利用了这些概念之前停止。
第四章:深入 Python-Arduino 原型设计
在完成第一个项目后,你成功开始了 Python-Arduino 接口。我们还通过数字引脚将多个硬件组件(即运动传感器和 LED)与 Arduino 接口。在项目过程中,你通过使用简单的 Python 方法学习了更多关于 Firmata 协议的知识,这些方法帮助你建立 Arduino 板与 Python 程序之间的连接。当你处理复杂项目时,你需要比基本方法更多的东西来实现项目及其相关电子组件所需的不同功能。本章旨在为你提供全面的接口体验,以便你从下一章开始着手解决难题。我们描述了 Python-Arduino 和 Arduino 到组件级别的各种接口协议。本章还包括了这些协议的实践示例,包括适当的代码和电路图。在本章中,我们将涵盖以下主要主题:
-
原型设计简介
-
详细描述将 Arduino 功能导入 Python 的
pyFirmata方法的介绍 -
使用 Firmata 进行 Python-Arduino 接口的示例,包括电位计、蜂鸣器、直流电机和伺服电机等基本电子元件
-
互连集成电路(I2C)协议简介及 I2C 组件(如温度传感器(TMP102)和光传感器(BH1750))的原型设计示例
原型设计
就让我们暂时回顾一下上一章中我们构建的项目。这个项目目标非常简单,我们能够相当轻松地完成它。然而,这个项目显然还没有准备好成为一款消费产品,因为它缺乏显著的功能,最重要的是,它不是一个可以重复生产的稳健产品。关于你当前的项目,你可以知道的是,它是一个个人使用的 DIY 项目,或者只是一个可以进一步开发成为优秀产品的模型。
现在,如果你打算开发一款商业产品或者一个真正稳健且可扩展的 DIY 项目,你必须考虑首先制作一个模型。在这个阶段,你需要设想产品所需的功能以及部署这些功能所需的组件数量。原型设计基本上是一种快速创建你设想中的想法的工作模型的方法,在将其开发成完整功能的项目或产品之前。在原型设计过程中开发的证明概念原型让你能够确定你想法的可行性,在某些情况下,它还帮助你探索项目的潜力。原型设计或功能模型制作过程对于任何行业都是至关重要的,而不仅仅是电子行业。
在电子领域,原型设计可以在将组件与计算机接口的第一个阶段使用,而不是直接投入大量资源进行原理图设计、PCB 制造和开发完整的代码库。这一阶段有助于你识别电路设计中的主要缺陷,并检查所选组件的相互兼容性。
幸运的是,Arduino 及其周围的现有软件支持极大地简化了电子原型设计。在接下来的章节中,我们将介绍各种辅助函数和接口练习,以帮助你进行自己的项目。这些示例或模板设计得如此之好,以至于它们可以用作更大项目的蓝图。
在深入研究这些原型设计示例之前,让我们了解我们将在本章中探索的两个不同的接口抽象:
-
将 Arduino 与 Python 接口:我们已经学习了使用 Firmata 协议进行 Python-Arduino 接口的最简单方法。在 Arduino 板上,Firmata 协议是通过 StandardFirmata 固件实现的,而在 Python 端,我们使用了 Firmata 库,即
pyFirmata或pyMata。另一种 Python-Arduino 接口方法包括在 Python 程序中使用自定义 Arduino 脚本和pySerial库的简单但非标准的串行命令。使用计算机网络在 Python 和 Arduino 之间建立通信也是可能的,这在本书的后续部分将进行介绍。 -
将电子组件与 Arduino 接口:第二个接口抽象与 Arduino 和物理组件相关。正如我们已经做的那样,各种电子组件可以通过数字或模拟引脚简单地与 Arduino 板接口。这些组件处理数字或模拟信号。Arduino 板上的一些数字引脚支持特定硬件设备的 PWM 通信。其他替代接口方法包括 I2C 和 串行外设接口(SPI)通信。I2C 方法在本章的最后部分进行了全面解释。
使用 pyFirmata 方法
pyFirmata 包提供了有用的方法来弥合 Python 和 Arduino 的 Firmata 协议之间的差距。尽管这些方法是用具体的例子描述的,但你也可以以各种不同的方式使用它们。本节还详细描述了一些在先前项目中未使用的方法,并列出了缺失的功能。
设置 Arduino 板
要在 Python 程序中使用pyFirmata设置你的 Arduino 板,你需要特别遵循我们已覆盖的步骤。我们将整个设置过程所需的代码分散到每个步骤中的小代码片段中。在编写代码时,你必须仔细使用适合你应用场景的代码片段。你总是可以参考包含完整代码的示例 Python 文件。在我们继续之前,首先确保你的 Arduino 板安装了最新版本的StandardFirmata程序,并且已经连接到你的电脑:
-
根据所使用的 Arduino 板,首先将适当的
pyFirmata类导入到 Python 代码中。目前,内置的pyFirmata类仅支持 Arduino Uno 和 Arduino Mega 板:from pyfirmata import Arduino在 Arduino Mega 的情况下,使用以下代码行:
from pyfirmata import ArduinoMega -
在我们开始执行与处理引脚相关的任何方法之前,你需要正确设置 Arduino 板。要执行此任务,我们首先需要识别 Arduino 板连接的 USB 端口,并将此位置以字符串对象的形式分配给一个变量。对于 Mac OS X,端口字符串应大致如下所示:
port = '/dev/cu.usbmodemfa1331'对于 Windows 系统,使用以下字符串结构:
port = 'COM3'在 Linux 操作系统的案例中,使用以下代码行:
port = '/dev/ttyACM0'端口的位置可能会根据你的电脑配置而有所不同。你可以通过 Arduino IDE,如第二章中所述,使用以下方法来识别你的 Arduino USB 端口的正确位置:使用 Firmata 协议和 pySerial 库。
-
一旦你导入了 Arduino 类并将端口分配给一个变量对象,就是时候将 Arduino 与
pyFirmata结合并关联到另一个变量上了:board = Arduino(port)同样,对于 Arduino Mega,使用这个:
board = ArduinoMega(port) -
Arduino 板与
pyFirmata之间的同步需要一些时间。在先前的赋值和下一组指令之间添加睡眠时间可以帮助避免与串行端口缓冲相关的问题。添加睡眠时间最简单的方法是使用内置的 Python 方法sleep(time):from time import sleep sleep(1)sleep()方法接受秒作为参数,可以使用浮点数来提供特定的睡眠时间。例如,对于 200 毫秒,将是sleep(0.2)。
到目前为止,你已经成功使用pyFirmata将 Arduino Uno 或 Arduino Mega 板同步到电脑上。如果你想使用 Arduino 板的其它变体(除了 Arduino Uno 或 Arduino Mega)怎么办?
-
在
pyFirmata中,任何板布局都被定义为字典对象。以下是一个 Arduino 板字典对象的示例:arduino = { 'digital' : tuple(x for x in range(14)), 'analog' : tuple(x for x in range(6)), 'pwm' : (3, 5, 6, 9, 10, 11), 'use_ports' : True, 'disabled' : (0, 1) # Rx, Tx, Crystal } -
对于您的 Arduino 板变体,您必须首先创建一个自定义字典对象。要创建此对象,您需要了解您的板硬件布局。例如,Arduino Nano 板的布局类似于常规 Arduino 板,但它有八个而不是六个模拟端口。因此,前面的字典对象可以定制如下:
nano = { 'digital' : tuple(x for x in range(14)), 'analog' : tuple(x for x in range(8)), 'pwm' : (3, 5, 6, 9, 10, 11), 'use_ports' : True, 'disabled' : (0, 1) # Rx, Tx, Crystal } -
由于您之前已经同步了 Arduino 板,请使用
setup_layout(layout)方法修改板的布局:board.setup_layout(nano)此命令将修改同步的 Arduino 板的默认布局为 Arduino Nano 布局或任何其他您已定制的字典对象变体。
配置 Arduino 引脚
一旦您的 Arduino 板同步完成,就是时候配置将要作为程序一部分使用的数字和模拟引脚了。Arduino 板具有数字 I/O 引脚和模拟输入引脚,可以用于执行各种操作。正如我们所知,其中一些数字引脚也具备 PWM 功能。
直接方法
在我们开始向这些引脚写入或读取任何数据之前,我们必须首先将这些引脚的模式分配好。在上一章中我们使用的基于 Arduino 草图的方法中,我们使用了pinMode函数,即pinMode(11, INPUT)进行此操作。同样,在pyFirmata中,此分配操作是通过在板对象上使用mode方法来执行的,如下面的代码片段所示:
from pyfirmata import Arduino
from pyfirmata import INPUT, OUTPUT, PWM
# Setting up Arduino board
port = '/dev/cu.usbmodemfa1331'
board = Arduino(port)
# Assigning modes to digital pins
board.digital[13].mode = OUTPUT
board.analog[0].mode = INPUT
pyFirmata库包括用于INPUT和OUTPUT模式的类,在使用之前需要导入。前面的示例展示了将数字引脚 13 委托为输出,将模拟引脚 0 委托为输入。模式方法是在使用digital[]和analog[]数组索引分配配置的 Arduino 板变量上执行的。
pyFirmata库还支持PWM和SERVO等附加模式。PWM模式用于从数字引脚获取模拟结果,而SERVO模式帮助数字引脚设置轴的角度在 0 到 180 度之间。PWM和SERVO模式将在本章后面的详细示例中解释。如果您使用这些模式中的任何一个,请从pyFirmata库导入相应的类。一旦从pyFirmata包中导入了这些类,就可以使用以下代码行为适当的引脚分配模式:
board.digital[3].mode = PWM
board.digital[10].mode = SERVO
注意
在电子学中,PWM 是一种信号调制技术,广泛用于向组件提供受控的功率。在处理数字信号时,PWM 技术通过利用方波和控制信号宽度来获得模拟结果。
如我们所知,Arduino 板的数字引脚只能有两种状态,5V(高)和 0V(低)。通过控制高和低之间的切换模式,可以生成方波脉冲,从而产生脉冲。通过改变这些脉冲的宽度,可以模拟 0V 到 5V 之间的任何电压。如图所示,我们有一个占空比为 25%的方波。这意味着在占空期的这段时间内,我们正在模拟 0.25 * 5V = 1.25V:

Arduino 语言使用analogWrite()函数支持 PWM,其中 0V 到 5V 之间的电压范围以 0 到 255 之间的线性比例缩放。例如,50%的占空比(模拟 2.5V)转换为值 127,可以在 Arduino 中编码为analogWrite(13,127)。在这里,数字13代表支持 PWM 的 Arduino Uno 板上的数字引脚。同样,20%的占空比(1V)转换为analogWrite(13,64)。
分配引脚模式
配置引脚的直接方法主要用于单行执行调用。在一个包含大量代码和复杂逻辑的项目中,将具有特定功能的引脚分配给变量对象是非常方便的。使用这样的赋值,你可以在程序中利用分配的变量执行各种操作,而不是每次需要使用该引脚时都调用直接方法。在pyFirmata中,可以使用get_pin(pin_def)方法执行此赋值:
from pyfirmata import Arduino
port = '/dev/cu.usbmodemfa1311'
board = Arduino(port)
# pin mode assignment
ledPin = board.get_pin('d:13:o')
get_pin()方法允许你使用pin_def字符串参数('d:13:o')分配引脚模式。pin_def的三个组成部分是引脚类型、引脚号和引脚模式,由冒号(:)运算符分隔。引脚类型(模拟和数字)分别用a和d表示。get_pin()方法支持三种模式,i表示输入,o表示输出,p表示 PWM。在之前的代码示例中,'d:13:o'指定数字引脚 13 为输出。在另一个示例中,如果你想将模拟引脚 1 设置为输入,参数字符串将是'a:1:i'。
操作引脚
现在你已经配置了 Arduino 引脚,是时候开始使用它们执行操作了。在操作引脚时,支持两种不同类型的方法:报告方法和 I/O 操作方法。
报告数据
当引脚在程序中配置为模拟输入引脚时,它们开始向串行端口发送输入值。如果程序不利用这些传入的数据,数据将开始在串行端口缓冲,并迅速溢出。pyFirmata库提供了报告和迭代方法来处理这种现象。
使用enable_reporting()方法将输入引脚设置为开始报告。在执行引脚的读取操作之前,需要使用此方法:
board.analog[3].enable_reporting()
读取操作完成后,可以将引脚设置为禁用报告:
board.analog[3].disable_reporting()
在前面的例子中,我们假设你已经设置了 Arduino 板并配置了模拟引脚 3 的模式为 INPUT。
pyFirmata 库还提供了 Iterator() 类来读取和处理串行端口上的数据。当使用模拟引脚时,我们建议你在主循环中启动一个迭代器线程来更新引脚值为最新值。如果不使用迭代器方法,缓冲数据可能会溢出你的串行端口。这个类在 pyFirmata 包的 util 模块中定义,并在代码中使用之前需要导入:
from pyfirmata import Arduino, util
# Setting up the Arduino board
port = 'COM3'
board = Arduino(port)
sleep(5)
# Start Iterator to avoid serial overflow
it = util.Iterator(board)
it.start()
手动操作
由于我们已经配置了 Arduino 引脚到合适的模式和它们的报告特性,我们可以开始监控它们。pyFirmata 库为配置的引脚提供了 write() 和 read() 方法。
write() 方法
write() 方法用于向引脚写入一个值。如果引脚的模式设置为 OUTPUT,则值参数是一个布尔值,即 0 或 1:
board.digital[pin].mode = OUTPUT
board.digital[pin].write(1)
如果你使用了替代的引脚模式赋值方法,你可以按照以下方式使用 write() 方法:
ledPin = board.get_pin('d:13:o')
ledPin.write(1)
在 PWM 信号的情况下,Arduino 接受一个介于 0 和 255 之间的值,该值表示 0 到 100 百分比之间的占空期长度。pyFirmata 库提供了一个简化的方法来处理 PWM 值,而不是介于 0 和 255 之间的值,你可以提供一个介于 0 和 1.0 之间的浮点值。例如,如果你想有一个 50 百分比的占空期(2.5V 的模拟值),你可以使用 write() 方法指定 0.5。pyFirmata 库将负责转换并发送适当的值,即 127,通过 Firmata 协议发送到 Arduino 板:
board.digital[pin].mode = PWM
board.digital[pin].write(0.5)
类似地,对于间接赋值方法,你可以使用一些类似于以下代码片段的代码:
pwmPin = board.get_pin('d:13:p')
pwmPin.write(0.5)
如果你使用的是 SERVO 模式,你需要提供介于 0 和 180 度之间的值。不幸的是,SERVO 模式仅适用于直接赋值引脚,并且将来将适用于间接赋值:
board.digital[pin].mode = SERVO
board.digital[pin].write(90)
read() 方法
read() 方法在指定的 Arduino 引脚上提供一个输出值。当使用 Iterator() 类时,使用此方法接收到的值是串行端口上最新更新的值。当你读取一个数字引脚时,你可以得到两个输入之一,HIGH 或 LOW,这将在 Python 中转换为 1 或 0:
board.digital[pin].read()
Arduino 的模拟引脚将 0 到 +5V 之间的输入电压线性转换为 0 到 1023。然而,在 pyFirmata 中,0 到 +5V 之间的值被线性转换为 0 和 1.0 的浮点值。例如,如果模拟引脚上的电压为 1V,Arduino 程序将测量一个大约为 204 的值,但当你使用 Python 中的 pyFirmata 的 read() 方法时,你会收到浮点值 0.2。
其他功能
除了已经描述的方法之外,pyFirmata库还提供了一些用于额外定制的实用函数,具体如下:
-
servo_config(pin,min_pulse=544,max_pulse=2400,angle=0):此方法有助于设置带有进一步定制的SERVO模式,例如最小脉冲值、最大脉冲值和起始角度。可以使用angle参数设置伺服电机的初始角度。 -
pass_time(seconds):此方法提供与默认 Python 的time模块提供的默认方法sleep()类似的功能。然而,pass_time函数提供了非阻塞的超时(以秒为单位)。 -
get_firmata_version():此函数返回一个元组,包含 Arduino 板上 Firmata 协议的版本:board.get_firmata_version() -
exit():我们建议你在完成代码运行后从pyFirmata断开 Arduino 板。这将释放串行端口,然后可以被其他程序使用:board.exit()
即将提供的函数
pyFirmata库目前正在开发中,并持续接收更新以添加和改进各种方法。尽管大多数本地 Arduino 方法都通过 Firmata 协议在pyFirmata库中可用,但仍有一些函数尚未提供或正在开发中,具体如下:
-
pulseIn/pulseOut:这些是 Arduino 的本地函数,等待 Arduino 引脚达到指定的值。等待时间以微秒为单位返回。这种方法被 Ping(超声波距离测量)传感器广泛使用。使用pyFirmata实现此方法需要对标准的 Firmata 协议进行重大更改。 -
shiftIn/shiftOut:这些函数逐位将数据字节移入或移出。pyFirmata库缺少对这些函数的支持,可以使用各种 Python 编程技巧实现。
使用 Firmata 进行原型模板设计
本节的目标是提供原型模板,同时解释各种 Python 方法和编程技术。它试图涵盖一些最流行的传感器,并使用 DIY Arduino 项目中使用的编码示例。本节旨在利用 Firmata 协议实现这些 Python 程序。它还包括各种 Python 编程范式,如处理不定循环、创建自定义函数、处理随机数、从提示符获取手动输入等。这些原型模板设计得易于集成到大型项目中,或者可以作为围绕它们开发的大型项目的蓝图。在前一节中,你已经全面了解了pyFirmata包,我们将在接下来的示例中仅使用那些pyFirmata函数。本章后面将介绍支持 Firmata 协议的另一种 Python 库。
电位器 – 从模拟输入进行连续观察
电位器是一个可以通过旋钮控制的变阻器。它有三个端子,其中两个是 Vref 和地,而第三个提供可变输出。电位器的输出根据旋钮的位置在提供的电压之间变化。在 Arduino 中,您可以将电位器连接到+5V 和板子的地引脚,以提供供电电压。当可变端子与 Arduino 模拟输入接口时,这些电压值分别转换为 0 到 1023。在pyFirmata的情况下,模拟观察的值在 0 到 1 之间转换。
这个包含电位器的编码模板可以应用于需要外部手动控制系统项目的项目中。电位器的输出可以转换为 Arduino 的模拟输入,用于控制执行器,如电机或 LED。在某些情况下,输入也可以通过将其值应用于变量来控制程序的流程。
连接
将电位器的输出连接到如图所示的模拟引脚 A0。通过将电位器的 Vref 和地端子分别连接到+5V 和 Arduino 板的地来完成电路:

Python 代码
假设您已经将StandardFirmata固件上传到 Arduino 板,您需要在计算机上运行 Python 代码来完成其与电位器的接口。本书代码包中提供了一个名为potentiometer.py的 Python 代码模板,可以帮助您开始这个示例,该模板可以从www.packtpub.com/books/content/support/1961下载。让我们打开这个文件来了解程序。如您所见,我们正在使用pyFirmata库以及其他 Python 模块,如time和os:
from pyfirmata import Arduino, util
from time import sleep
import os
在程序的第二步中,我们初始化 Arduino 板并开始在其上执行Iterator()函数:
port = 'COM3'
board = Arduino(port)
sleep(5)
it = util.Iterator(board)
it.start()
一旦初始化了板子,我们需要为模拟引脚0分配一个角色,因为它将被用作输入引脚。我们使用get_pin()方法为模拟引脚0分配角色:
a0 = board.get_pin('a:0:i')
现在,作为主程序的一部分,我们需要持续监控我们刚刚定义的引脚a0上的电位器输出。我们使用while语句创建一个不定循环,用于脚本读取和打印模拟输入。这个不定while循环的问题在于,当程序被中断时,程序将无法正确关闭,并且它不会通过执行board.exit()方法来释放板子。为了避免这种情况,我们将使用 Python 编程范式中的另一个控制语句,称为try/except:
try:
while True:
p = a0.read()
print p
except KeyboardInterrupt:
board.exit()
os._exit()
使用此语句,程序将一直运行 while 循环,直到发生键盘中断,即 Ctrl + C,程序将执行 except 语句下的脚本。这包括使用 board.exit() 释放板子,以及使用 os._exit() 方法退出程序。总之,程序将持续打印电位器的输出,直到有人按下 Ctrl + C 来中断程序。
注意
Python 中的 try/except 语句提供了一种非常高效的方式来捕获异常。建议在整个开发过程中使用此语句来巧妙地调试你的程序。你可以从以下链接中了解 Python 错误和异常:
蜂鸣器 – 生成声音警报模式
数字蜂鸣器传感器被用于各种需要警报通知的应用。当它们接收到数字 HIGH 值(即 +5V)的供电时,这些传感器会发出声音,这可以通过使用 Arduino 的数字引脚来实现。类似于前一章中的 LED 示例,它们与 Arduino 的接口非常简单。然而,我们不是执行简单的数字输出,而是实现 Python 编程技巧来生成不同的声音模式和产生各种声音效果。相同的代码模板也可以用来生成不同的 LED 闪烁模式。
注意
在 www.amazon.com/Arduino-Compatible-Speaker-arduino-sensors/dp/B0090X0634 可以找到模拟数字蜂鸣器。
连接
如以下电路图所示,将传感器板的 VCC 和地连接到 5V 和 Arduino 板的地引脚。将传感器的信号引脚通过 220 欧姆电阻连接到数字引脚 2。你可以使用任何数字引脚来连接蜂鸣器。只需确保更新 Python 代码以反映你选择的引脚。

Python 代码
在代码示例中,使用时间延迟数组生成了两种不同的声音模式。为了执行这些操作,我们将实现一个自定义的 Python 函数,该函数将接受引脚号、重复时间和模式号作为输入。在我们跳到解释代码之前,让我们打开代码文件夹中的程序文件,buzzerPattern.py。在代码的开头,你可以找到将被主程序以适当选项调用的 Python 函数buzzerPattern()。由于这个函数是整个程序的核心,让我们尝试理解它。该函数包含两个硬编码的模式数组,pattern1和pattern2。每个数组包含一个秒内蜂鸣器的开启和关闭时间,即模式的占空比。例如,在pattern1中,0.8代表蜂鸣器需要开启的时间,而0.2代表相反的情况。函数将重复指定的recurrence次该蜂鸣器模式。一旦启动了值为recurrence的for循环,函数将检查函数参数中的模式号并执行该模式。我们使用flag变量交替使用pattern数组中的元素来控制蜂鸣器。一旦整个重复循环完成,如果蜂鸣器是开启的,我们将完全关闭蜂鸣器,并使用exit()方法安全地断开板:
def buzzerPattern(pin, recurrence, pattern):
pattern1 = [0.8, 0.2]
pattern2 = [0.2, 0.8]
flag = True
for i in range(recurrence):
if pattern == 1:
p = pattern1
elif pattern == 2:
p = pattern2
else:
print "Please enter valid pattern. 1 or 2."
exit
for delay in p:
if flag is True:
board.digital[pin].write(1)
flag = False
sleep(delay)
else:
board.digital[pin].write(0)
flag = True
sleep(delay)
board.digital[pin].write(0)
board.exit()
小贴士
如果你想要更改时间延迟或实现完全不同的模式,你可以对pattern数组进行操作。
程序的其余部分相对简单,因为它包含导入库和初始化 Arduino 板的代码。一旦板被初始化,我们将使用输入参数(2, 10, 1)执行buzzerPattern()函数。这个参数将要求函数在引脚号 2 上播放pattern1 10 次:
from pyfirmata import Arduino
from time import sleep
port = '/dev/cu.usbmodemfa1331'
board = Arduino(port)
sleep(5)
buzzerPattern(2, 10, 1)
直流电机 – 使用 PWM 控制电机速度
直流电机在机器人应用中得到了广泛的应用。它们的电压规格范围很广,取决于应用。在这个例子中,我们使用 5V 直流电机,因为我们想使用 Arduino 板本身供电。由于 Arduino 数字引脚只能有两种状态,即HIGH(+5V)或LOW(0V),仅使用OUTPUT模式无法控制电机的速度。作为解决方案,我们将通过支持 PWM 的数字引脚实现PWM模式。在使用pyFirmata时,配置为PWM模式的引脚可以接受 0 到 1.0 之间的任何浮点输入值,分别代表 0V 和 5V。
连接
根据负载,直流电机有时会消耗大量电流,并损害 Arduino 板。为了避免由于任何意外的大电流消耗而对 Arduino 板造成损害,我们将使用晶体管作为开关,它只使用少量电流来控制直流电机中的大量电流。为了完成以下图中显示的电路连接,您需要一个 NPN 晶体管(TIP120、N2222 或类似型号),一个二极管(1N4001 或类似型号)和一个 220 欧姆的电阻与您的直流电机连接。将晶体管的基极连接到也支持 PWM 模式的数字引脚 3。将剩余的组件按照图中的显示连接起来:

注意
要了解更多关于晶体管端子(集电极、发射极和基极)的信息,以及将晶体管引脚与其相应的端子关联起来,您可以参考它们的规格书或以下网站:
Python 代码
用于直流电机的 Python 配方dcMotorPWM.py位于本书的代码包中,可以从www.packtpub.com/books/content/support/1961下载。打开 Python 文件,以进一步了解如何使用 PWM 控制直流电机的速度。自定义函数dcMotorControl()接受电机速度和时间持续时间作为输入参数,如以下代码片段所述:
def dcMotorControl(r, deltaT):
pwmPin.write(r/100.00)
sleep(deltaT)
pwmPin.write(0)
就像之前的例子一样,我们使用类似的代码来导入必要的库并初始化 Arduino 板。初始化后,我们将数字引脚 3 的模式设置为PWM,这可以从get_pin('d:3:p')方法的利用中看出。这段代码反映了我们在上一节中学到的间接模式引脚模式分配方式:
# Set mode of pin 3 as PWM
pwmPin = board.get_pin('d:3:p')
作为从用户收集手动输入的一部分,我们正在运行一个组合的try/except语句(在退出时释放板)和while语句(从用户获取连续输入)。代码模板引入了input()方法,从 Python 的交互式终端获取自定义值(电机速度和运行电机的持续时间)。一旦从用户那里获取了这些值,程序就会调用dcMotorControl()函数来执行电机动作:
try:
while True:
r = input("Enter value to set motor speed: ")
if (r > 100) or (r <= 0):
print "Enter appropriate value."
board.exit()
break
t = input("How long? (seconds)")
dcMotorControl(r, t)
except KeyboardInterrupt:
board.exit()
os._exit
LED – 使用 PWM 控制 LED 亮度
在之前的模板中,我们使用 PWM 控制了直流电机的速度。也可以用同样的方法控制 LED 的亮度。在这个模板中,我们不会要求用户输入亮度,而是将使用 Python 模块 random。我们将使用此模块生成一个介于 1 和 100 之间的随机数,然后将其写入引脚,并随机改变 LED 的亮度。这个 randint() 函数是 random 模块提供的非常有用的功能,并且在通过快速发送随机信号测试原型时被广泛使用。
注意
randint() 函数采用 randint(startValue, endValue) 语法,并返回介于 startValue 和 endValue 建立的范围内的随机整数。
连接
如同我们在上一章的项目中所用,我们需要一个上拉电阻将 LED 连接到 Arduino 引脚。如图所示,只需将 LED 的阳极(较长的一端)通过一个 220 欧姆的电阻连接到数字引脚 11,并将阴极(较短的一端)连接到地:

需要注意的是,Arduino Uno 上的数字引脚 11 也能够与数字引脚 3、5、6、9 和 10 一起执行 PWM。
Python 代码
本练习的标题为 ledBrightnessPWM.py 的 Python 代码位于本书的代码包中,可以从 www.packtpub.com/books/content/support/1961 下载。打开文件以探索代码。正如您在这段代码模板中看到的,在将值传递到 PWM 引脚之前,会随机选择一个介于 0 和 1.0 之间的浮点数。这种方法可以在给定的时间内生成随机 LED 亮度。这种做法可以用于生成各种其他测试项目的随机输入样本。
如您所见,代码的前几行导入了必要的库并初始化了板。尽管板变量 /dev/cu.usbmodemfa1311 是为 Mac OS X 选择的,但您可以在以下代码片段中使用您操作系统的特定变量名。您可以从本章开头的 设置 Arduino 板 部分获取有关选择此变量名的更多信息。
from pyfirmata import Arduino, INPUT, PWM
from time import sleep
import random
port = '/dev/cu.usbmodemfa1311'
board = Arduino(port)
sleep(5)
在这个例子中,我们正在使用直接方法分配引脚模式。正如您在以下代码片段中看到的,数字引脚 11 被分配到 PWM 模式:
pin = 11
board.digital[pin].mode = PWM
一旦分配了引脚模式,程序将使用 for 语句运行一个循环,同时随机生成一个介于 0 和 100 之间的整数,然后根据生成的数字向引脚发送适当的 PWM 值。执行此操作后,您将能够看到 LED 随机改变亮度,大约持续 10 秒:
for i in range(0, 99):
r = random.randint(1, 100)
board.digital[pin].write(r / 100.00)
sleep(0.1)
在完成循环后,你需要关闭 LED 最后一次,然后安全地断开 Arduino 板。在退出板子之前关闭 LED 或任何连接的传感器是一个好习惯,以防止任何传感器意外运行:
board.digital[pin].write(0)
board.exit()
注意
如果你想要均匀地使 LED 发光而不是随机改变其亮度,将for循环中的代码替换为以下代码片段。在这里,我们将 PWM 输入更改为递增变量i,而不是随机变量r:
for i in range(0, 99):
board.digital[pin].write(i / 100.00)
sleep(0.1)
伺服电机 – 将电机移动到特定角度
伺服电机是广泛应用于如云台相机控制、机械臂、移动机器人运动等需要精确电机轴运动的电子组件。这种对电机轴的精确控制是由于位置感应解码器,它是伺服电机组件的组成部分。标准伺服电机允许轴的角度在 0 到 180 度之间设置。pyFirmata库提供了可以在每个数字引脚上实现的SERVO模式。这个原型练习提供了一个模板和指南,用于将伺服电机与 Python 接口。
连接
通常,伺服电机有红色、黑色和黄色的线分别用于连接电源、地线和 Arduino 板的信号。将伺服电机的电源和地线连接到 5V 和 Arduino 板的地线。如图所示,将黄色信号线连接到数字引脚 13:

如果你想要使用其他任何数字引脚,确保你在下一节的 Python 程序中更改引脚号。一旦你完成了适当的连接,让我们继续到 Python 程序。
Python 代码
包含此代码的 Python 文件命名为servoCustomAngle.py,位于本书的代码包中,可以从www.packtpub.com/books/content/support/19610下载。在 Python 编辑器中打开此文件。与其他示例一样,程序的起始部分包含导入库和设置 Arduino 板的代码:
from pyfirmata import Arduino, SERVO
from time import sleep
# Setting up the Arduino board
port = 'COM5'
board = Arduino(port)
# Need to give some time to pyFirmata and Arduino to synchronize
sleep(5)
现在你已经准备好使用 Python 与 Arduino 板通信,让我们配置将要用来连接伺服电机的数字引脚。我们将通过将引脚 13 的mode设置为SERVO来完成此任务:
# Set mode of the pin 13 as SERVO
pin = 13
board.digital[pin].mode = SERVO
setServoAngle(pin,angle)自定义函数接受伺服电机连接的引脚和自定义角度作为输入参数。这个函数可以用作涉及伺服电机的大型项目的组成部分:
# Custom angle to set Servo motor angle
def setServoAngle(pin, angle):
board.digital[pin].write(angle)
sleep(0.015)
在本模板的主要逻辑中,我们希望逐步将电机轴向一个方向移动,直到达到可达到的最大角度(180 度),然后以相同的增量速度将其移回原始位置。在while循环中,我们将要求用户输入以继续此程序,该输入将通过raw_input()函数捕获。用户可以输入字符y以继续此程序,或输入任何其他字符以终止循环:
# Testing the function by rotating motor in both direction
while True:
for i in range(0, 180):
setServoAngle(pin, i)
for i in range(180, 1, -1):
setServoAngle(pin, i)
# Continue or break the testing process
i = raw_input("Enter 'y' to continue or Enter to quit): ")
if i == 'y':
pass
else:
board.exit()
break
在处理所有这些原型设计示例时,我们使用了通过使用数字和模拟引脚将传感器与 Arduino 连接的直接通信方法。现在,让我们熟悉另一种在 Arduino 和传感器之间广泛使用的通信方法,即 I2C 通信。
使用 I2C 协议进行原型设计
在上一节中,传感器或执行器通过数字、模拟或 PWM 引脚直接与 Arduino 通信。这些方法被大量基本、低级传感器使用,你将在未来的 Arduino 项目中广泛使用它们。除了这些方法之外,还有许多基于集成电路(IC)的流行传感器,它们需要不同的通信方式。这些基于 IC 的高级传感器利用 I2C 或 SPI 总线方法与微控制器通信。由于我们将在即将到来的项目中使用基于 I2C 的传感器,本节将仅涵盖 I2C 协议和实际示例,以便更好地理解该协议。一旦你了解了 I2C 协议的基础,你就可以非常快速地学习 SPI 协议。
注意
你可以从以下链接了解更多关于 SPI 协议和 Arduino 支持的 SPI 库的信息:
1982 年,飞利浦公司需要找到一种简单高效的方法来在微控制器和电视上的外围芯片之间建立通信,这导致了 I2C 通信协议的开发。I2C 协议使用仅两条线将微控制器或 CPU 连接到大量低速外围设备。此类外围设备或传感器的例子包括 I/O 设备、A/D 转换器、D/A 转换器、EEPROM 以及许多类似设备。I2C 使用主从设备的概念,其中微控制器是主设备,外围设备是从设备。以下图示显示了 I2C 通信总线的示例:

如前一个图所示,主设备包含两条双向线:串行数据线(SDA)和串行时钟线(SCL)。在 Arduino Uno 的情况下,模拟引脚 4 和 5 提供了 SDA 和 SCL 的接口。需要注意的是,这些引脚配置会随着 Arduino 板的不同版本而改变。作为从设备工作的外围传感器连接到这些线,这些线也由上拉电阻支持。主设备负责在 SCL 上生成时钟信号,并与从设备初始化通信。从设备接收时钟并响应主设备发送的命令。
从设备顺序并不重要,因为主设备通过从设备的部分地址与其通信。为了初始化通信,主设备会在总线上发送以下类型之一的消息,并带有特定的部分地址:
-
在从设备上写入数据的单个消息
-
读取从设备数据的单个消息
-
读取从设备数据的多个消息
要在 Arduino 编程中支持 I2C 协议,Arduino IDE 配备了一个名为 Wire 的默认库。您可以通过在程序开头添加以下代码行将此库导入到您的 Arduino 绘图中:
#include <Wire.h>
为了初始化 I2C 通信,Wire 库使用以下函数的组合在从设备上写入数据:
Wire.beginTransmission(0x48);
Wire.write(0);
Wire.endTransmission();
这些从设备使用独特的部分地址进行区分。正如前一个示例中所示,0x48 是连接的从设备的部分地址。
Wire 库还提供了 Wire.read() 和 Wire.requestFrom() 函数来读取和请求从设备的数据。这些函数将在下一节中详细解释。
注意
您可以从以下链接中了解更多关于 I2C 协议和 Wire 库的信息:
Arduino I2C 接口示例
为了练习 I2C 协议的原型设计练习,让我们利用两个流行的 I2C 传感器,这些传感器可以检测环境中的温度和光照。作为理解 I2C 消息的第一步,我们将使用 Arduino 绘图进行 I2C 接口操作,稍后,我们将使用 Python 开发类似的功能。
为 TMP102 温度传感器编写的 Arduino 代码
TMP102 是广泛使用的数字传感器之一,用于测量环境温度。与传统的模拟温度传感器(如 LM35 或 TMP36)相比,TMP102 提供了更好的分辨率和精度。以下是 TMP102 的图片:

上一张图片展示了一个带有 TMP102 传感器可用引脚的扩展板。请注意,您获得的 TMP102 传感器可能具有与图片中显示的不同的引脚布局。在连接任何电路之前,始终建议检查您的传感器扩展板的数据表。如图所示,TMP102 传感器支持 I2C 协议,并配备了 SDA 和 SCL 引脚。将 Arduino Uno 板的模拟引脚 4 和 5 连接到 TMP102 传感器的 SDA 和 SCL 引脚。此外,按照以下图示连接 +5V 和地线。在这个例子中,我们使用 Arduino Uno 板作为主设备,TMP102 作为从设备外设,其中 TMP102 的部分地址为十六进制的 0x48:

注意
您可以从 SparkFun Electronics 购买 TMP102 传感器扩展板,链接为www.sparkfun.com/products/11931。
您可以从www.sparkfun.com/datasheets/Sensors/Temperature/tmp102.pdf获取该板的数据表。
现在,使用 USB 线将您的 Arduino 板连接到计算机,并在 Arduino IDE 中创建一个新的草图,使用以下代码片段。一旦在 Arduino IDE 中选择了适当的串行端口和板类型,上传并运行代码。如果所有步骤都按描述执行,则在执行时,您将在 串行监视器 窗口中看到温度读数,以 摄氏度 和 华氏度 显示:
#include <Wire.h>
int partAddress = 0x48;
void setup(){
Serial.begin(9600);
Wire.begin();
}
void loop(){
Wire.requestFrom(partAddress,2);
byte MSB = Wire.read();
byte LSB = Wire.read();
int TemperatureData = ((MSB << 8) | LSB) >> 4;
float celsius = TemperatureData*0.0625;
Serial.print("Celsius: ");
Serial.println(celsius);
float fahrenheit = (1.8 * celsius) + 32;
Serial.print("Fahrenheit: ");
Serial.println(fahrenheit);
delay(500);
}
在前面的代码片段中,Wire.requestFrom(partAddress,2) 函数从从设备 TMP102 请求两个字节。从设备发送数据字节到主设备,这些字节被 Wire.read() 函数捕获,并存储为两个不同的位:最高有效位(MSB)和最低有效位(LSB)。这些字节被转换为整数值,然后通过乘以从数据表中获得的 TMP102 传感器的增量分数,将其转换为实际的摄氏读数。TMP102 是最容易与 Arduino 接口的 I2C 传感器之一,因为可以通过简单的 I2C 请求方法获取传感器值。
Arduino 编码用于 BH1750 光传感器
BH1750 是一种数字光传感器,用于测量特定区域内的可见光量。尽管各种 DIY 项目使用简单的光敏电阻作为便宜的替代品,但 BH1750 传感器因其高分辨率和精度而广泛应用于各种应用。环境光,也称为光通量或流明,以流明为单位测量。BH1750 传感器支持使用部分地址 0x23 的 I2C 通信,如果您使用多个 BH1750 传感器,则次要地址为 0x5C。以下是一个典型的 BH1750 扩展板的图片:

将 BH1750 扩展板的 SDA 和 SCL 引脚连接到 Arduino Uno 板的模拟引脚 4 和 5,如以下电路图所示。同时,完成+5V 和地线的连接,如以下图所示:

在前面的例子中,我们使用了Wire库中的函数来完成 I2C 通信。尽管BH1750是一个简单且方便的 I2C 传感器,但在具有多种测量能力的传感器情况下,直接使用Wire库进行编码并不方便。在这种情况下,您可以使用由制造商或开源社区开发的特定于传感器的 Arduino 库。对于BH1750,我们将演示如何使用此类库来辅助 I2C 编码。在我们能够使用此库之前,我们必须将其导入到 Arduino IDE 中。了解将库导入到 Arduino IDE 的过程非常重要,因为您将在未来重复此过程来安装其他库。执行以下步骤将BH1750库导入到您的 Arduino IDE 中:
-
下载并解压第七章,中期项目 – 一个便携式 DIY 恒温器,代码示例在一个文件夹中。
-
打开 Arduino IDE 并导航到草图 | 导入库… | 添加库…。
-
当被要求选择目录时,请前往下载文件中的
BH1750文件夹,然后点击选择。 -
要检查您的库是否已安装,导航到草图 | 导入库…,并在下拉列表中查找BH1750。
-
最后,重启 Arduino IDE。
提示
如果您使用的是版本 1.0.4 或更早版本的 Arduino IDE,您可能无法在菜单中找到导入库…选项。在这种情况下,您需要遵循
arduino.cc/en/Guide/Libraries上的教程。
BH1750库有一个直接获取环境光值的方法。让我们使用内置的代码示例来测试这个库。
在重启 Arduino IDE 后,导航到文件 | 示例 | BH1750,打开BH1750test Arduino 草图。这应该在 Arduino IDE 中打开以下代码片段。设置合适的串行端口并将代码上传到您的 Arduino 板。一旦代码执行,您将能够使用 Arduino IDE 的串行监视器检查光通量(lux)值。请确保串行监视器配置为 9600 波特率:
#include <Wire.h>
#include <BH1750.h>
BH1750 lightMeter;
void setup(){
Serial.begin(9600);
lightMeter.begin();
Serial.println("Running...");
}
void loop() {
uint16_t lux = lightMeter.readLightLevel();
Serial.print("Light: ");
Serial.print(lux);
Serial.println(" lx");
delay(1000);
}
如您从前面的代码片段中看到的,我们通过包含 Wire.h 文件中的 BH1750.h 文件来导入 BH1750 库。这个库提供了 readLightLevel() 函数,它将从传感器获取环境光值并提供为整数。由于 Arduino 代码以 1000 毫秒的延迟在循环中运行,lux 值将每秒从传感器获取并发送到串行端口。您可以在 串行监视器 窗口中观察到这些值。
PyMata 用于快速 I2C 原型设计
我们一直使用 pyFirmata 作为我们的默认 Python 库来接口 Firmata 协议。pyFirmata 库是一个非常有用的 Python 库,用于开始使用 Firmata 协议,因为它提供了许多简单而有效的方法来定义 Firmata 端口及其角色。由于这些原因,我们在前面的部分中广泛使用了 pyFirmata 进行快速原型设计。尽管 pyFirmata 支持模拟、数字、PWM 和 SERVO 模式,并提供了易于使用的方法,但它对 I2C 协议的支持有限。
在本节中,我们将使用一个名为 PyMata 的不同 Python Firmata 库来熟悉基于 Python 的 I2C 传感器的原型设计。PyMata 库支持常规的 Firmata 方法,并提供对 I2C 消息协议的全面支持。
PyMata 可以通过 Setuptools 轻松安装,我们在前面的章节中使用它来安装其他 Python 库。我们假设您已经在计算机上安装了 Setuptools 和 pip。让我们开始执行以下步骤:
-
要在 Windows 计算机上安装
PyMata,请在命令提示符中执行以下命令:C:\> easy_install.exe pymata -
如果您正在使用 Linux 或 Mac OS X,请在终端中使用以下命令安装
PyMata库:$ sudo pip install pymata -
如果一切设置正确,此过程将无错误完成。您可以通过在 Python 的交互式提示符中打开并导入
PyMata来确认PyMata:>>> import PyMata -
如果前述命令的执行失败,您需要检查安装过程是否有任何错误。解决错误并重复安装过程。
使用 PyMata 接口 TMP102
为了利用 PyMata 的功能,您需要确保您的 Arduino 板安装了标准的 firmata 固件,就像 pyFirmata 库一样。在我们继续解释 PyMata 的功能之前,让我们首先运行以下代码片段。按照上一节中的说明连接 TMP102 温度传感器。使用 Arduino IDE,导航到 文件 | 示例 | Firmata,并将标准 Firmata 脚本上传到您的 Arduino 板。现在,使用以下代码片段创建一个 Python 可执行文件。如果需要,将端口的值(COM5)更改为操作系统所需的适当端口名称。最后,运行程序:
import time
from PyMata.pymata import PyMata
#Initialize Arduino using port name
port = PyMata("COM5")
#Configure I2C pin
port.i2c_config(0, port.ANALOG, 4, 5)
# One shot read asking peripheral to send 2 bytes
port.i2c_read(0x48, 0, 2, port.I2C_READ)
# Wait for peripheral to send the data
time.sleep(3)
# Read from the peripheral
data = port.i2c_get_read_data(0x48)
# Obtain temperature from received data
TemperatureSum = (data[1] << 8 | data[2]) >> 4
celsius = TemperatureSum * 0.0625
print celsius
fahrenheit = (1.8 * celsius) + 32
print fahrenheit
firmata.close()
在执行前面的代码片段时,你将能够在华氏和摄氏温度下看到温度读数。正如你从代码中的内联注释中可以看到,使用 PyMata 初始化端口的第一步是使用 PyMata 构造函数。PyMata 通过 i2c_config() 函数支持配置 I2C 引脚。PyMata 还通过 i2c_read() 和 i2c_write() 函数支持同时读写操作。
使用 PyMata 接口 BH1750
对于 BH1750 来说,前面的 PyMata 代码片段经过少量修改后可以用来获取环境光传感器数据。作为第一个更改,你想要将 TMP102 的部分地址(0x48)替换为 BH1750 的地址(0x23)在下面的代码片段中。你还需要将传感器接收到的原始值转换为 lux 值,使用给定的公式。完成这些修改后,从终端运行以下程序:
import time
from PyMata.pymata import PyMata
port = PyMata("COM5")
port.i2c_config(0, port.ANALOG, 4, 5)
# Request BH1750 to send 2 bytes
port.i2c_read(0x23, 0, 2, port.I2C_READ)
# Wait for BH1750 to send the data
time.sleep(3)
# Read data from BH1750
data = port.i2c_get_read_data(0x23)
# Obtain lux values from received data
LuxSum = (data[1] << 8 | data[2]) >> 4
lux = LuxSum/1.2
print str(lux) + ' lux'
firmata.close()
在运行前面的代码片段后,你将能够在终端看到环境光传感器的 lux 读数。这个过程可以用于大量 I2C 设备来读取注册信息。在复杂的 I2C 设备中,你必须遵循它们的规格书或示例来组织 I2C 的读写命令。
有用的 pySerial 命令
标准的 Firmata 协议和 Python 的 Firmata 库对于测试或快速原型化 I2C 传感器非常有用。尽管它们有很多优点,但基于 Firmata 的项目面临以下缺点:
-
实时执行中的延迟:基于 Firmata 的方法需要一系列的串行通信消息来接收和发送数据,这增加了额外的延迟并降低了执行速度。
-
不必要的空间:Firmata 协议包含大量额外的代码来支持各种其他 Arduino 功能。在一个定义良好的项目中,你实际上并不需要完整的函数集。
-
有限支持:尽管 Firmata 的某个版本包括 I2C 支持,但如果不添加延迟,实现复杂的 I2C 函数相当困难。
总结来说,你总是可以使用基于 Firmata 的方法来快速原型化你的项目,但在你从事生产级或高级项目时,你可以使用替代方法。在这些情况下,你可以使用由 Python 的串行库 pySerial 支持的定制 Arduino 代码,以启用非常特定的功能通信。在本节中,我们将介绍一些有用的 pySerial 方法,如果你必须直接使用库的话。
连接到串行端口
一旦你将 Arduino 连接到电脑的 USB 端口,你就可以使用 Serial 类在 Python 代码中打开端口,如下面的代码示例所示:
import serial
port = serial.Serial('COM5',9600, timeout=1)
除了端口号和波特率之外,你还可以使用Serial()指定一系列串行端口参数,如timeout、bytesize、parity、stopbits等。在执行pySerial库中的任何其他命令之前,必须初始化串行端口。
从端口读取一行
一旦串行端口打开,你可以开始使用readline()读取端口。在初始化端口时,readline()函数需要指定超时,否则代码可能会因异常而终止:
line = port.readline()
readline()函数将处理端口中每个以换行符\n终止的行。
清除端口以避免缓冲区溢出
在使用pySerial时,有必要清除输入缓冲区以避免缓冲区溢出并保持实时操作:
port.flushInput()
如果端口的波特率较高且输入数据处理较慢,可能会发生缓冲区溢出,降低执行速度并使体验变得缓慢。
关闭端口
完成过程后关闭串行端口是一种良好的编码实践。这种做法可以在 Python 代码终止后消除端口阻塞问题:
port.close()
摘要
在本章中,你学习了成功将 Arduino 板与 Python 接口所需的重要方法。你还被介绍了各种具有实际应用的原型代码模板。这些原型模板帮助我们学习新的 Python 编程范式和 Firmata 方法。在章节的后面部分,我们进一步深入原型设计,通过学习更多关于传感器与 Arduino 板之间建立通信的不同方式。尽管我们通过这些原型示例涵盖了大量的编程概念,但本章的目标是让你熟悉接口问题,并为你的项目提供快速解决方案。
我们假设你现在已经熟悉使用 Python 和 Arduino 测试你的传感器或项目原型。现在是时候开始着手创建具有复杂 Python 功能(如用户控制、图表和绘图)的应用程序了。在下一章中,我们将为你的 Python-Arduino 项目开发自定义图形用户界面(GUI)。
第五章:使用 Python GUI
在前四章中,我们使用了 Python 交互式提示符或 Arduino 串行监视器来观察结果。在提示符上使用基于文本的输出方法可能对基本的快速原型设计有用,但当你进行高级原型设计和展示你的原型或最终产品时,你需要一个看起来不错且用户友好的界面。GUI 可以帮助用户理解你的硬件项目的各个组件,并轻松与之交互。它还可以帮助你验证项目的结果。
Python 拥有多个广泛使用的 GUI 框架,如Tkinter、wxPython、PyQt、PySide和PyGTK。这些框架几乎都具备创建专业应用程序所需的所有功能。由于涉及到的复杂性,这些框架对于初学者 Python 程序员来说有不同的学习曲线。现在,由于这本书致力于 Arduino 项目中的 Python 编程,我们无法花费大量时间学习特定框架的细节。相反,我们将根据以下标准选择我们的界面库:
-
安装方便,快速上手
-
实现简单,学习成本低
-
资源消耗最小
满足所有这些要求的框架是Tkinter(wiki.python.org/moin/TkInter)。Tkinter也是所有 Python 安装中默认的标准 GUI 库。
注意
虽然Tkinter是 Python 的事实上的 GUI 包,但你也可以从它们各自的官方网站了解更多关于前面提到的其他 GUI 框架的信息,如下所示:
-
wxPython:
www.wxpython.org/ -
PyGTK:
www.pygtk.org/ -
PySide:
qt-project.org/wiki/PySide
学习 Tkinter 进行 GUI 设计
Tkinter,即Tk界面,是Tk GUI 工具包的跨平台 Python 接口。Tkinter在 Python 上提供了一个薄层,而Tk提供了图形小部件。Tkinter是一个跨平台库,作为主要操作系统的 Python 安装包的一部分进行部署。对于 Mac OS X 10.9,Tkinter与默认 Python 框架一起安装。对于 Windows,当你从安装文件安装 Python 时,Tkinter会与之一起安装。
Tkinter旨在以最小的编程努力开发图形应用程序,同时足够强大以支持大多数 GUI 应用程序功能。如果需要,Tkinter还可以通过插件进行扩展。自Tk版本 8.0 发布以来,Tkinter通过Tk提供了操作系统自然的视觉和感觉。
要测试你的Tk工具包当前版本,请在 Python 提示符中使用以下命令:
>>> import Tkinter
>>> Tkinter._test()
你将看到一个类似于以下截图中的图像,其中包含有关你的Tk版本的信息:

如果你遇到获取此窗口的任何问题,请检查你的 Python 安装并重新安装它,因为没有Tkinter库和Tk工具包,你将无法在本章中继续前进。
Tkinter界面支持各种窗口小部件来开发 GUI。以下表格描述了我们将在本章中使用的一些重要窗口小部件:
| 窗口小部件 | 描述 |
|---|---|
Tk() |
这是每个程序所需的根窗口小部件 |
Label() |
这显示文本或图像 |
Button() |
这是一个简单的按钮,可以用来执行动作 |
Entry() |
这是一个文本字段,用于向程序提供输入 |
Scale() |
这通过拖动滑块提供数值 |
Checkbox() |
这允许你通过勾选框在两个值之间切换 |
注意
可以从docs.python.org/2/library/tk.html获取Tkinter函数和方法的详细描述,这些函数和方法实现了Tk工具包提供的多数功能。
你的第一个 Python GUI 程序
正如我们在前面的章节中讨论的,学习任何编程语言的第一程序通常包括打印Hello World!。现在,由于我们开始 Python GUI 编程,让我们先在 GUI 窗口中打印相同的字符串,而不是在提示符中。
只为了开始 GUI 编程,我们将执行一个 Python 程序,然后跳入解释代码的结构和细节。让我们使用以下代码行创建一个 Python 可执行文件,命名为helloGUI.py,然后运行它。执行过程应该在没有依赖错误的情况下完成:
import Tkinter
# Initialize main windows with title and size
top = Tkinter.Tk()
top.title("Hello GUI")
top.minsize(200,30)
# Label widget
helloLabel = Tkinter.Label(top, text = "Hello World!")
helloLabel.pack()
# Start and open the window
top.mainloop()
在成功执行前面的代码片段后,你应该会看到一个以下窗口。如你所见,Hello World!字符串已打印在窗口中,窗口标题为Hello GUI:

那么,究竟发生了什么?如你所见,我们从代码片段中逐个实例化了各种Tkinter窗口小部件以获得这个结果。这些窗口小部件是使用Tkinter开发的任何 Python GUI 应用程序的构建块。让我们从第一个也是最重要的窗口小部件Tk()开始。
根窗口小部件 Tk()和顶级方法
Tk() 小部件初始化一个带有标题栏的主空窗口。这是一个根小部件,每个程序只需要一次。主窗口的装饰和样式来自操作系统的环境。因此,当您在不同的操作系统上运行相同的 Tkinter 代码时,您将得到相同的窗口和标题栏,但风格不同。
一旦创建了根小部件,您就可以执行一些顶层方法来装饰、描述或调整此窗口的大小。在代码中,我们使用 title() 方法设置主窗口的标题。这个 title() 方法接受一个字符串作为输入参数:
Top = Tkinter.Tk()
top.title("Hello GUI")
接下来,我们在主窗口上调用 minsize() 方法,通过 (width, height) 参数设置窗口的最小尺寸:
top.minsize(200,30)
类似地,您也可以使用 maxsize() 方法来指定主窗口应该有的最大尺寸。在 minsize() 和 maxsize() 方法中,width 和 height 的值以像素为单位提供。
一旦整个程序实例化完成,就需要 mainloop() 函数来启动事件循环:
top.mainloop()
如果代码没有进入主事件循环,您将看不到任何其他小部件,包括主窗口。事件循环将一直活跃,直到窗口被手动关闭或调用退出方法。
您可能对更新窗口、程序化关闭它、在网格中排列小部件等问题有各种疑问。肯定有比之前指定的更多顶层方法。
Label() 小部件
除了 Tk() 之外,代码中使用的另一个小部件是 Label()。Tkinter 小部件是部件层次结构的一部分,其中 Label() 是根小部件 Tk() 的子部件。此小部件不能在没有指定根小部件或标签需要显示的主窗口的情况下调用。此小部件的主要用途是在主窗口中显示文本或图像。在下面的代码行中,我们使用它来显示 Hello World! 字符串:
helloLabel = Tkinter.Label(top, text = "Hello World!")
在这里,我们创建并初始化了一个名为 helloLabel 的标签对象,它有两个输入参数:指定根小部件的 top 变量和一个 text 字符串。Label() 小部件高度可定制,并接受各种配置参数以调整宽度、边框、背景和对齐方式作为选项。涉及这些定制的示例将在接下来的章节中介绍。您可以在effbot.org/tkinterbook/label.htm了解更多关于支持的输入参数。
Pack 几何管理器
Pack 几何管理器按行和列组织小部件。要使用它,Tkinter 需要为每个小部件调用 pack() 方法,以便小部件在主窗口上可见:
helloLabel.pack()
Pack 几何管理器可以被所有Tkinter控件使用,除了根控件,用于在根窗口中组织控件。在多个控件的情况下,如果未指定控件的位置,Pack 管理器将它们安排在同一个根窗口中。Pack 管理器易于实现,但在自定义程度上有限制。一个有助于创建复杂布局的替代几何管理器称为Grid,这将在接下来的章节中解释。
在接下来的编码练习中,我们将介绍更多的控件及其相关方法。在这些练习中,我们将通过实际应用来解释每个单独的控件,以便您更好地理解使用案例。
Button()小部件 – 将 GUI 与 Arduino 和 LEDs 接口
现在您已经有了创建 Python 图形界面的第一次动手经验,让我们将其与 Arduino 集成。Python 使得将各种异构包相互接口变得容易,这正是您将要做的。在下一个编码练习中,我们将使用Tkinter和pyFirmata使 GUI 与 Arduino 协同工作。在这个练习中,我们将使用Button()控件来控制与 Arduino 板接口的 LED。
在我们开始练习之前,让我们构建我们将需要用于所有后续程序的电路。以下是我们使用的电路的 Fritzing 图,其中我们使用了两种不同颜色的 LED 和上拉电阻。将这些 LED 连接到 Arduino Uno 板上的数字引脚 10 和 11,如图所示:

注意
在使用本节和后续章节中提供的程序时,您必须根据您的操作系统替换用于定义板变量的 Arduino 端口。要找出您的 Arduino 板连接到哪个端口,请遵循第二章中提供的详细说明,使用 Firmata 协议和 pySerial 库。同时,如果您打算使用除 10 和 11 之外的任何引脚,请确保在代码中提供正确的引脚号。对于某些练习,您将需要使用 PWM 引脚,所以请确保您有正确的引脚。
在上一个练习中,我们要求你将整个代码片段作为一个 Python 文件运行。由于程序长度和复杂性的原因,在接下来的练习中可能无法这样做。因此,我们将这些练习组装在程序文件中,可以从 第四章 的代码文件夹中访问,深入 Python-Arduino 原型设计,可以从 www.packtpub.com/books/content/support/1961 下载。对于 Button() 小部件练习,请从 第四章 的代码文件夹中打开 exampleButton.py 文件,深入 Python-Arduino 原型设计。代码包含三个主要部分:
-
pyFirmata库和 Arduino 配置 -
按钮的
Tkinter小部件定义 -
当你按下按钮时执行的 LED 闪烁函数
如以下代码片段所示,我们首先使用 pyFirmata 方法导入库并初始化 Arduino 板。对于这个练习,我们只将与一个 LED 一起工作,并且只为它初始化了 ledPin 变量:
import Tkinter
import pyfirmata
from time import sleep
port = '/dev/cu.usbmodemfa1331'
board = pyfirmata.Arduino(port)
sleep(5)
ledPin = board.get_pin('d:11:o')
注意
由于我们在这个章节的所有练习中都使用 pyFirmata 库,请确保你已经将标准 Firmata 草图的最新版本上传到你的 Arduino 板上。
在代码的第二部分,我们已经将根 Tkinter 小部件初始化为 top 并提供了一个标题字符串。我们还使用 minsize() 方法固定了窗口的大小。为了更熟悉根小部件,你可以尝试调整窗口的最小和最大尺寸:
top = Tkinter.Tk()
top.title("Blink LED using button")
top.minsize(300,30)
Button() 小部件是一个标准的 Tkinter 小部件,主要用于从用户那里获取手动、外部的输入刺激。像 Label() 小部件一样,Button() 小部件可以用来显示文本或图像。与 Label() 小部件不同的是,当它被按下时,可以与之关联动作或方法。当按钮被按下时,Tkinter 执行由 command 选项指定的方法或命令:
startButton = Tkinter.Button(top,
text="Start",
command=onStartButtonPress)
startButton.pack()
在这个初始化中,与按钮关联的函数是 onStartButtonPress,按钮标题显示为 "Start" 字符串。同样,top 对象指定了父对象或根小部件。一旦按钮实例化,你需要使用 pack() 方法使其在主窗口中可用。
在前面的代码行中,onStartButonPress()函数包含了闪烁 LED 和更改按钮状态的脚本。按钮状态可以是NORMAL、ACTIVE或DISABLED。如果没有指定,任何按钮的默认状态都是NORMAL。ACTIVE和DISABLED状态在需要避免按钮重复按下的应用程序中非常有用。在用write(1)方法打开 LED 后,我们将使用sleep(5)函数添加 5 秒的时间延迟,然后再用write(0)方法关闭它:
def onStartButtonPress():
startButton.config(state=Tkinter.DISABLED)
ledPin.write(1)
# LED is on for fix amount of time specified below
sleep(5)
ledPin.write(0)
startButton.config(state=Tkinter.ACTIVE)
在程序结束时,我们将执行mainloop()方法来启动Tkinter循环。直到这个函数被执行,主窗口不会出现。
要运行代码,对 Arduino 的board变量进行适当的更改并执行程序。以下带有按钮和标题栏的屏幕截图将作为程序的输出。点击开始按钮将在指定的时间延迟内打开 Arduino 板上的 LED。同时,当 LED 亮起时,你将无法再次点击开始按钮。现在,在这个特定的程序中,我们没有提供足够的代码来安全地断开 Arduino 板,这将在接下来的练习中介绍。

Entry()小部件 – 提供手动用户输入
在之前的练习中,你使用了一个按钮来在 Arduino 板上闪烁 LED,持续固定的时间。假设你想更改这个固定的时间延迟,并指定一个根据你的应用程序需求的价值。要执行此操作,你需要一个可以接受自定义值并将其转换为延迟的小部件。就像任何其他 GUI 框架一样,Tkinter提供了一个名为Entry()的类似小部件的接口,我们将在下一个练习中使用它。
保持与之前练习相同的 Arduino 和 LED 配置,并打开exampleEntry.py文件。在代码的开头,你会找到与之前练习中使用的相同的 Arduino 板和 LED 引脚配置。进入下一阶段,你将能够看到以下代码片段,它定义了根小部件。在这个代码片段中,我们将主窗口的标题更改,以反映练习的前提。使用独特的字符串作为窗口标题将有助于你在处理一个应用程序中的多个窗口时,根据它们的属性来区分这些窗口:
top = Tkinter.Tk()
top.title("Specify time using Entry")
虽然可以通过指定父小部件作为唯一参数轻松初始化Entry()小部件,但它也支持大量参数来自定义小部件。例如,在我们的练习中,我们使用bd参数来指定小部件边框的宽度,以及width来提供小部件的预期宽度。你可以在effbot.org/tkinterbook/entry.htm了解更多关于可用选项的信息:
timePeriodEntry = Tkinter.Entry(top,
bd=5,
width=25)
timePeriodEntry.pack()
timePeriodEntry.focus_set()
startButton = Tkinter.Button(top,
text="Start",
command=onStartButtonPress)
startButton.pack()
在前面的代码行中,我们在主窗口中初始化了两个小部件对象:timePeriodEntry用于Entry()小部件,以及我们在上一个练习中使用的startButton,用于Button()小部件。Pack 几何管理器始终将图形指针设置为主窗口中最后添加的小部件。我们可以使用focus_set()方法手动将图形指针的焦点移到timePeriodEntry小部件。
与上一个练习中的onStartButtonPress()函数相反,这个函数没有使用时间延迟修复。相反,它从timePeriodEntry对象中获取值。你可以使用get()方法从timePeriodEntry对象中获取输入的值,并使用float()函数将其转换为浮点值。正如你可以在以下代码片段中看到的那样,我们使用这个浮点值作为从开启状态切换 LED 关闭的时间延迟:
def onStartButtonPress():
# Value for delay is obtained from the Entry widget input
timePeriod = timePeriodEntry.get()
timePeriod = float(timePeriod)
startButton.config(state=Tkinter.DISABLED)
ledPin.write(1)
sleep(timePeriod)
ledPin.write(0)
startButton.config(state=Tkinter.ACTIVE)
一旦你理解了初始化Entry()小部件的过程以及从中获取自定义值的方法,让我们执行代码。
当你运行这个练习时,你应该能看到一个类似于以下截图所示的窗口。在秒数中输入一个时间延迟值,然后点击开始按钮来查看 LED 上的结果。基本上,当按钮被按下时,程序将调用onStartButtonPress()函数,并使用这个值来产生时间延迟。

Scale()小部件 – 调整 LED 的亮度
在本节中,我们将编写一些代码来使用 Python GUI 改变 LED 的亮度。之前,我们了解到你可以使用 Arduino 的数字引脚通过 PWM 产生模拟输出。虽然你可以使用Entry()小部件为 PWM 信号提供一个一次性值,但有一个可以动态提供这个值的小部件将是有用的。由于亮度可以在 0 到 100 百分比之间变化,因此使用介于 0 和 100 之间的滑块是有意义的。Tkinter库通过Scale()小部件提供了这种滑动界面。
在我们努力改变 LED 的亮度和提供模拟输入时,我们将使用支持 PWM 的数字引脚。在之前的练习中,我们使用了数字引脚 11,它已经支持 PWM。如果您使用的是与之前提供的不同的自定义电路版本,我们建议您将其更改为支持 PWM 的引脚。现在,是时候打开程序文件exampleScale.py进行这个练习了。
程序的第一个阶段涉及导入必要的库并使用pyFirmata初始化 Arduino 板,这与之前的练习几乎相同。根据操作系统和您使用的端口更改用于指定端口变量的字符串。我们还将实例化根窗口,并为这个练习提供独特的标题,就像在之前的练习中做的那样。这个程序的部分通常会在大量的练习中重复出现,您可以参考之前的练习以获取更多信息。
在下一阶段,我们将继续构建我们之前开发的代码,为 LED 提供手动时间延迟。我们还将使用相同的Entry()小部件来获取时间间隔作为输入:
timePeriodEntry = Tkinter.Entry(top,
bd=5,
width=25)
timePeriodEntry.pack()
timePeriodEntry.focus_set()
Scale()小部件提供了一个可以沿着固定刻度移动的滑动旋钮,以提供数值输出。此刻度的起始值和结束值使用from_和to选项提供。此滑动条的朝向也可以使用orient选项进行配置,其中朝向的可接受值为HORIZONTAL和VERTICAL。然而,在使用这些值之前,您必须从Tkinter库中导入HORIZONTAL和VERTICAL常量。
如果没有提供选项,默认小部件使用从 0 到 100 的刻度以及垂直方向。在我们的程序中,我们使用了水平方向作为orient选项的演示。一旦定义了小部件对象brightnessScale,您就必须使用pack()将其添加到 Pack 几何管理器中:
brightnessScale = Tkinter.Scale(top,
from_=0, to=100,
orient=Tkinter.HORIZONTAL)
brightnessScale.pack()
为了启动过程并重用之前的代码,我们保留了startButton小部件的实例化和onStartButtonPress函数的原样。然而,函数的属性被更改以适应Scale()小部件:
startButton = Tkinter.Button(top,
text="Start",
command=onStartButtonPress)
startButton.pack()
在这个版本的onStartButtonPress()函数中,我们将通过在brightnessScale小部件对象上使用get()方法来获取ledBrightness值,其中get()方法将返回滑动条当前位置的价值。由于 PWM 输入需要介于 0 和 1 之间的值,而获取的滑动条值介于 0 和 100 之间,我们将通过除以 100 将滑动条值转换为适当的 PWM 输入。然后,这个新值将与write()方法一起使用,这将最终根据timePeriodEntry值提供的时间周期打开 LED。
def onStartButtonPress():
timePeriod = timePeriodEntry.get()
timePeriod = float(timePeriod)
ledBrightness = brightnessScale.get()
ledBrightness = float(ledBrightness)
startButton.config(state=Tkinter.DISABLED)
ledPin.write(ledBrightness/100.0)
sleep(timePeriod)
ledPin.write(0)
startButton.config(state=Tkinter.ACTIVE)
关于 Scale() 小部件的信息,您可以参考 effbot.org/tkinterbook/scale.htm。现在,运行 exampleScale.py 文件。您将能够看到以下截图,其中包含 Entry() 和 Scale() 小部件。输入时间延迟,将滑块拖动到您想要的亮度,然后点击 开始 按钮:

您将能够看到 LED 以 Scale() 小部件设置的亮度点亮。一旦经过给定的时间延迟后 LED 关闭,您可以将滑块重置到另一个位置以动态改变亮度值。
网格几何管理器
在上一个练习中,我们使用 Pack 几何管理器和 pack() 方法向根窗口添加了三个不同的小部件。我们没有积极组织这些小部件,但 Pack 管理器自动将它们垂直排列。在设计有意义的界面时,您需要以适当的顺序排列这些小部件。如果您查看先前的输出窗口,很难识别每个小部件的功能或它们与其他小部件的关系。为了设计直观的 GUI,您还需要使用适当的标签描述这些小部件。作为解决方案,Tkinter 提供了一种组织小部件的替代方法,称为 网格几何管理器。
网格几何管理器提供了一个 二维(2D)表格界面来排列小部件。2D 表格的每一行和列组合产生的单元格都可以用作小部件的位置。您将在下一个编程练习中学习 grid() 类提供的各种选项,以组织小部件。从本章代码文件夹中打开 exampleGridManager.py 文件。从功能上讲,此文件包含我们在上一个练习中构建的相同程序。然而,我们添加了更多的 Label() 小部件,并使用网格几何管理器对它们进行组织,以简化 GUI 并使其更有用。
如您在代码中所观察到的,timePeriodEntry 对象(一个 Entry() 小部件)现在使用的是 grid() 方法而不是 pack() 方法。grid() 方法使用列和行选项进行初始化。这些选项提供的值决定了 timePeriodEntry 对象将被放置的单元格位置。
另一方面,我们使用 Label() 小部件创建了一个标签对象,并将其放置在 Entry() 小部件旁边的同一行。标签包含一个使用 text 选项指定的描述字符串。在用 grid() 方法将其放置在单元格后,小部件在该单元格中居中排列。要更改此对齐方式,您可以使用 sticky 选项与 N、E、S 和 W 中的一个或多个值,即北、东、南和西:
timePeriodEntry = Tkinter.Entry(top, bd=5)
timePeriodEntry.grid(column=1, row=1)
timePeriodEntry.focus_set()
Tkinter.Label(top, text="Time (seconds)").grid(column=2, row=1)
我们已经重复了这种将小部件放置在单元格中并使用Label()小部件对其进行描述的实践,用于Scale()和Button()小部件的对象:
brightnessScale = Tkinter.Scale(top, from_=0, to=100, orient=Tkinter.HORIZONTAL)
brightnessScale.grid(column=1, row=2)
Tkinter.Label(top, text="Brightness (%)").grid(column=2, row=2)
startButton = Tkinter.Button(top, text="Start", command=onStartButtonPress)
startButton.grid(column=1, row=3)
如前述代码片段所示,我们在具有相似列值的同时为小部件使用不同的行值。因此,我们的小部件将在同一列中组织,并且它们将在同一行的下一列中拥有它们的描述标签。如果您想检查这种组织模式,可以跳转到输出窗口。
到目前为止,我们一直依赖用户手动关闭主窗口。然而,您可以通过创建另一个Button()小部件并通过它调用关闭此窗口的方法。在这个编码练习中,我们有一个比上一个练习多的按钮,称为exitButton。与此按钮关联的command参数是quit,它结束由Tkinter方法top.mainloop()启动的循环并关闭 GUI:
exitButton = Tkinter.Button(top,
text="Exit",
command=top.quit)
exitButton.grid(column=2, row=3)
在此代码示例中,quit方法被初始化为command选项,也可以作为方法调用:
top.quit()
在我们继续下一步之前,对代码进行适当的更改并运行程序。您将看到一个类似于以下截图所示的窗口:

红色虚线是在后来插入的,以帮助您识别网格,它们不会出现在运行程序打开的窗口中。现在,由于旁边有描述标签,您可以清楚地识别每个小部件的作用。在打开的窗口中,使用开始和退出按钮来执行相关操作的同时,调整时间和亮度值。从下一个练习开始,我们将开始定期使用grid()方法来排列小部件。
Checkbutton()小部件 – 选择 LED
在开发复杂项目时,您会遇到需要依赖用户从给定值集中选择单个或多个选项的情况。例如,当您有多个 LED 与 Arduino 板连接时,您希望用户选择需要打开的 LED 或 LEDs。这种程度的定制使您的界面更具交互性和实用性。Tkinter库提供了一个名为Checkbutton()的标准小部件的接口,它允许从给定选项中进行手动选择过程。
在这个练习中,我们将处理您在开始时连接到 Arduino 板的绿色和红色 LED。这个练习的整个 Python 程序位于代码文件夹中,文件名为exampleCheckbutton.py。使用您一直在使用的相同编辑器打开文件。此程序实现了Checkbutton()小部件,以便在点击开始按钮时,用户可以选择红色和/或绿色 LED。
为了理解整个程序逻辑,让我们从初始化和导入库开始。如您所见,现在我们有两个数字引脚的分配,分别为 10 号和 11 号引脚,分别命名为redPin和greenPin。Arduino 板初始化的代码没有变化:
port = '/dev/cu.usbmodemfa1331'
board = pyfirmata.Arduino(port)
sleep(5)
redPin = board.get_pin('d:10:o')
greenPin = board.get_pin('d:11:o')
在我们使用Checkbutton()小部件的过程中,我们使用了一个非常有用的Tkinter变量类,称为IntVar()。Tkinter变量可以告诉系统变量值何时发生变化。为了更好地理解Tkinter变量类及其在我们练习中的具体应用,请查看程序中的以下代码片段:
redVar = Tkinter.IntVar()
redCheckBox = Tkinter.Checkbutton(top,
text="Red LED",
variable=redVar)
redCheckBox.grid(column=1, row=1)
Checkbutton()小部件允许用户在两个不同的值之间进行选择。这些值通常是1(开启)或0(关闭),使Checkbutton()小部件成为一个开关。为了捕获这个选择,需要在小部件定义中使用variable选项。可以使用Tkinter变量类之一初始化变量,即IntVar()。
如您所见,使用IntVar()类实例化的redVar变量对象在定义Checkbutton()小部件redCheckButton时用于variable选项。因此,对redCheckButton对象的任何操作都将转换为对redVar变量对象的操作。由于IntVar()是Tkinter类,它将自动通过Checkbutton()小部件处理变量值的任何变化。因此,建议在Checkbutton()小部件中使用Tkinter变量类而不是默认的 Python 变量。在为红色 LED 定义Checkbutton()小部件之后,我们按照以下代码片段所示,为绿色 LED 重复了此过程:
greenVar = Tkinter.IntVar()
greenCheckBox = Tkinter.Checkbutton(top,
text="Green LED",
variable=greenVar)
greenCheckBox.grid(column=2, row=1)
此程序还包含开始和退出按钮及其与onStartButtonPress和top.quit()函数的关联,类似于我们在之前的练习中使用它们的方式。当被调用时,onStartButtonPress函数将使用get()方法获取IntVar()变量redVar和greenVar的值。在这种情况下,当复选框被选中时,复选框小部件的变量值将是1,否则是0。这将使程序能够通过检查或取消选中小部件,使用write()方法将值1或0发送到 Arduino 引脚,最终打开或关闭 LED:
def onStartButtonPress():
redPin.write(redVar.get())
greenPin.write(greenVar.get())
如您所见,代码还实现了一个额外的停止按钮,用于关闭使用开始按钮打开的 LED:
stopButton = Tkinter.Button(top,
text="Stop",
command=onStopButtonPress)
stopButton.grid(column=2, row=2)
与此按钮关联的onStopButtonPrerss()函数通过在两个引脚上使用write(0)关闭两个 LED:
def onStopButtonPress():
redPin.write(0)
greenPin.write(0)
由于您现在已经了解了 Tkinter 变量和 Checkbutton() 小部件,让我们运行 Python 程序 exampleCheckbutton.py。如下一截图所示,GUI 有两个 Checkbutton() 小部件,分别用于红色和绿色 LED。由于 Checkbutton() 小部件有单独的初始化,用户可以同时检查红色和绿色 LED。Tkinter 还提供了类似的 Radiobutton() 和 Listbox() 小部件,用于您只想从给定选项中选择单个值的情况。

注意
您可以从以下网页了解更多关于 Radiobutton() 和 Listbox() 小部件的信息:
Label() 小部件 – 监控 I/O 引脚
Arduino 项目通常涉及实时系统,并需要持续监控数字和模拟引脚的输入值。因此,如果这些值在图形界面上显示,它们需要定期更新或当引脚状态改变时更新。
如果您观察之前的 GUI 练习,您会注意到我们在代码的末尾使用 mainloop() 初始化根窗口,这启动了 Tkinter 循环并使用更新后的值初始化了所有小部件。一旦初始化了 mainloop(),我们就不再使用任何其他 Tkinter 类或方法来定期用最新值更新小部件。
在这个练习中,我们将使用电位器为模拟引脚 0 提供可变输入,这将通过 Tkinter 的 Label() 小部件反映出来。为了更新标签并显示模拟输入的值,我们将实现一些 Python 和 Tkinter 技巧。由于我们使用电位器提供输入,您需要在跳转到 Python 程序之前,按照以下图示更改电路:

本练习的 Python 文件位于代码文件夹中的 workingWithLabels.py 文件。在进行此练习之前,请确保在定义 port 变量时已设置适当的 Arduino 板字符串。程序成功执行后,将显示以下截图,您可以通过点击开始按钮来启动电位器的输入值连续更新:

那么,我们是如何做到这一点的呢?这段代码包含比我们迄今为止所做更复杂的逻辑和不同的程序流程。如您从代码中看到的,我们使用一个名为flag的变量来跟踪退出按钮的状态,同时持续运行监控和更新值的while循环。为了正确理解程序,让我们首先熟悉以下新的Tkinter类和方法:
-
BooleanVar(): 就像我们用来跟踪整数值的IntVar()变量类一样,BooleanVar()是一个Tkinter变量类,用于跟踪布尔值的变化:flag = Tkinter.BooleanVar(top) flag.set(True)在前面的代码片段中,我们使用
BooleanVar()类创建了一个变量对象flag,并将该对象的价值设置为True。作为一个布尔对象,flag只能有两个值,True或False。Tkinter还提供了StringVar()和DoubleVar()类,分别用于字符串和双精度类型。因此,当点击开始按钮时,系统开始更新模拟读取值。退出按钮将
flag变量设置为false,中断while循环,并停止监控过程。 -
update_idletasks: 在使用 Python 中的Tkinter库时,您可以将 Python 代码链接到Tk()小部件中发生的任何更改。这个链接的 Python 代码被称为回调。update_idletasks方法调用所有空闲任务而不处理任何回调。此方法还根据需要重新绘制几何小部件:AnalogReadLabel.update_idletasks()在我们的练习中,这个方法可以用来持续更新标签,显示最新的电位计值。
-
update: 这个顶级方法处理所有挂起的事件和回调,并在必要时重新绘制任何小部件:top.update()我们使用这个方法与根窗口一起,以便它可以执行开始按钮的回调。
现在,让我们回到打开的 Python 程序。如您所见,除了通过get_pin()方法分配模拟引脚并在 Arduino 板上初始化Iterator()类之外,代码还包含我们在其他Tkinter小部件练习中使用的类似编程模式。在这个代码中,我们在onStartButtonPress()函数内部执行模拟引脚的读取操作。这个函数在执行引脚的read()操作时检查flag变量的状态,并在flag变量的值为True的情况下更新analogReadLabel()小部件的值。如果发现flag变量的值为False,函数将在断开 Arduino 板和关闭根窗口后退出。由于使用了while语句,这个过程将不断检查flag值,直到onExitButtonPress()函数通过将flag值更改为False来中断它:
def onStartButtonPress():
while True:
if flag.get():
analogReadLabel.config(text=str(a0.read()))
analogReadLabel.update_idletasks()
top.update()
else:
break
board.exit()
top.destroy()
onExitButtonPress()函数是从退出按钮调用的,它简单地使用set()方法将flag变量重置为False:
def onExitButtonPress():
flag.set(False)
使用 GUI 重制您的第一个 Python-Arduino 项目
为了刷新您的记忆,我想提醒您,我们创建了一个通过闪烁红色 LED 来生成警报的运动检测系统。在处理这个项目时,我们将接近传感器的状态打印到 Python 提示符中。在这个练习中,我们将使用您在之前的练习中学到的概念,并为我们的项目创建一个界面。
作为这个练习的一部分,您必须连接我们在第三章中使用的相同电路,第一个项目 – 触发 LED 的运动。在您继续之前,请确保您有与 PIR 传感器和 LED 完全相同的电路。一旦您的硬件准备就绪,请从本章代码文件夹中打开firstProjectWithGUI.py文件。在代码中,更改适当的端口值并运行项目的 GUI。
如您在引脚分配中看到的,我们现在有三个数字引脚——其中两个作为输出,一个作为输入。输出引脚分配给了红色和绿色 LED,而输入引脚分配给了 PIR 运动传感器。如果 PIR 传感器处于空闲模式,我们将执行一次read()操作来唤醒传感器:
pirPin = board.get_pin('d:8:i')
redPin = board.get_pin('d:10:o')
greenPin = board.get_pin('d:11:o')
pirPin.read()
代码实现的一个重要功能是blinkLED()函数。此函数更新用于描述运动传感器状态的Label()小部件。它还使用write()方法和插入的时间延迟闪烁物理 LED。作为输入参数,blinkLED()函数接受从函数调用中传递的引脚对象和消息字符串,其中引脚对象,即redPin或greenPin,应该是 LED 的引脚分配之一:
def blinkLED(pin, message):
MotionLabel.config(text=message)
MotionLabel.update_idletasks()
top.update()
pin.write(1)
sleep(1)
pin.write(0)
sleep(1)
另外两个与Tkinter相关的函数,onStartButtonPress()和onExitButtonPress(),基本上是从之前的练习中派生出来的。在这个版本的onStartButtonPress()中,如果flag变量为True并且使用pinPir.read()检测到运动,我们将调用blinkLED()函数:
def onStartButtonPress():
while True:
if flag.get():
if pirPin.read() is True:
blinkLED(redPin, "Motion Detected")
else:
blinkLED(greenPin, "No motion Detected")
else:
break
board.exit()
top.destroy()
程序还创建了两个按钮,开始和退出,以及一个标签,使用与之前练习中类似的方法。
从代码中您可以观察到,运动检测系统的逻辑仍然是相同的。我们只是增加了一层图形界面,使用Label()小部件连续显示检测到的运动状态。我们还添加了开始和退出按钮来控制项目执行周期。一旦运行代码,您将能够看到一个类似于以下截图的窗口。点击开始按钮并在运动传感器前挥手。如果传感器检测到运动,标签将从未检测到运动变为检测到运动。

摘要
现在你已经拥有了构建基本 GUI 以处理 Arduino 项目的实践经验。通过稍作修改所包含的练习,你可以使用它们为各种 Arduino 原型设计项目创建 GUI。在前两个练习中,我们在标签小部件中显示传感器输出为字符串。如果将这些数值以图表的形式展示并存储以供进一步分析,将会更有意义。这正是你将在下一章中执行的操作。
第六章:存储和绘制 Arduino 数据
连接到 Arduino 的传感器会产生大量的模拟和数字数据。模拟传感器产生数据点作为数值信息,而数字传感器产生布尔值,即 1(开启)或 0(关闭)。到目前为止,我们已将此数据作为字符串打印到命令提示符或显示在 GUI 中。数据是实时打印的,并未保存以供进一步分析。如果将数据打印为图表或图形,它将为我们提供快速理解并得出结论的有用信息。对于实时应用,图表甚至更有用,因为它们可以提供有关系统行为的信息,从而更好地理解数据。
本章围绕两个主要部分组织:存储 Arduino 传感器数据和绘制这些数据。我们将首先使用 Python 创建和操作文件。之后,我们将处理将 Arduino 数据存储为 CSV 文件格式的存储方法。在第二部分,您将介绍 Python 绘图库 matplotlib。然后,我们将处理涉及从保存的文件和实时传感器读取的数据绘图的示例。最后,我们将尝试将 matplotlib 绘图与我们在上一章中创建的 Tkinter 窗口集成。
在硬件组件方面,我们将使用熟悉的传感器,如电位计和 PIR 运动传感器,这些我们在前面的章节中使用过,因此,您不需要为这一章学习或购买任何额外的传感器。
在 Python 中处理文件
Python 提供了创建和修改文件的内置方法。与文件相关的 Python 操作在大量编程练习中非常有用。这些方法由标准 Python 模块提供,不需要安装额外的包。
open() 方法
open() 方法是 Python 中可用的一种默认方法,它是用于操作文件的最广泛使用的函数之一。现在,处理文件的第一步是打开它:
>>> f = open('test.txt', 'w')
此命令将在您启动 Python 解释器或执行代码的同一文件夹中创建一个 test.txt 文件。前面的命令使用 w 模式,该模式以写入方式打开文件或创建一个新文件(如果不存在)。可以与 open() 函数一起使用的其他模式如下表所示:
| 模式 | 描述 |
|---|---|
w |
以写入方式打开或创建文件。它会覆盖现有文件。 |
w+ |
以写入和读取方式打开或创建文件。它会覆盖现有文件。 |
r |
仅以读取方式打开文件。 |
r+ |
以读取和写入方式打开文件。 |
a |
以追加方式打开文件。它从文档的末尾开始追加。 |
a+ |
这将打开一个用于追加和读取的文件。它从文档的末尾开始追加。 |
注意
如果你在一个 Unix 或 Linux 环境中使用这些模式,请确保你有适当的读写权限。
write()方法
一旦文件以某种写入或追加模式打开,你就可以使用此方法开始向文件对象写入。write()方法只接受一个字符串作为输入参数。任何其他数据格式在写入之前都需要转换为字符串:
>>> f.write("Hello World!\n")
在这个例子中,我们正在写入以换行符\n结尾的Hello World!字符串。这个换行符在上一章中已经解释过,你可以在en.wikipedia.org/wiki/Newline上了解更多信息。
如果你想要将一系列字符串写入文件,也可以使用writelines()方法:
>>> sq = ["Python programming for Arduino\n", "Bye\n"]
>>> f.writelines(sq)
close()方法
close()方法关闭文件并释放文件所占用系统资源。一旦关闭,你将无法使用文件对象,因为它已经被刷新。完成文件操作后关闭文件是一个好习惯:
>>> f.close()
read()方法
这个read()方法从文件开头到结尾读取打开的文件内容。要使用此方法,你需要使用一种读取兼容的模式打开文件,例如w+、r、r+或a+:
>>> f = open('test.txt', 'r')
>>> f.read()
'Hello World!\nPython programming for Arduino\nBye\n'
>>> f.close()
由于read()方法会将整个文件内容加载到内存中,因此你可以使用可选的大小参数来避免在处理大文件时出现内存拥堵。作为替代方法,你可以使用readlines()方法逐行读取打开的文件内容:
>>> f = open('test.txt', 'r')
>>> l = f.readlines()
>>> print l
['Hello World!\n', 'Python programming for Arduino\n', 'Bye\n']
>>> f.close()
如前例所示,每个字符串都被打印为一个列表的元素,你可以单独访问它们。你可以通过这些方法来熟悉创建和修改文件。这些练习对于即将到来的编码练习将很有帮助。
with 语句 - Python 上下文管理器
虽然 with 语句可以用来覆盖由上下文管理器定义的代码块的执行,但它被广泛用于 Python 处理文件。假设你已经执行了前面的命令,并且有一个包含一些数据的test.txt文件,请在 Python 交互式提示符中执行以下命令:
>>> with open('test.txt', 'r') as f:
lines = f.readlines()
for l in lines:
print l
执行后,你将能够在命令提示符上看到文件的每一行。当与open()方法一起使用时,with 语句创建一个上下文管理器,它会执行包装的代码,并自动处理关闭文件。这是在 Python 中处理文件推荐的方法,我们将在所有练习中使用它。你可以在以下网站上了解更多关于 Python 上下文管理器的信息:
使用 CSV 文件存储数据
现在您已经了解了使用 Python 打开、操作和关闭文件的方法。在先前的示例中,我们使用了 Python 解释器和字符串数据来熟悉这些方法。但是,当涉及到从传感器数据中保存大量数值时,逗号分隔值(CSV)文件格式是除了文本之外最广泛使用的文件格式之一。正如其名所示,值是用逗号或其他分隔符(如空格或制表符)分隔和存储的。Python 有一个内置模块来处理 CSV 文件。
首先,使用以下代码片段创建一个 Python 文件并运行您的第一个 CSV 程序:
import csv
data = [[1, 2, 3], ['a', 'b', 'c'], ['Python', 'Arduino', 'Programming']]
with open('example.csv', 'w') as f:
w = csv.writer(f)
for row in data:
w.writerow(row)
您还可以从本章的代码文件夹中打开 csvWriter.py 文件,其中包含相同的代码。执行代码后,您将在与该文件相同的目录下找到一个名为 example.csv 的文件,其中包含用逗号分隔的数据。
如代码所示,CSV 模块在打开的文件上提供了 writer() 函数,该函数初始化一个 writer 对象。writer 对象接受一个序列或数组的数据(整数、浮点数、字符串等)作为输入,并使用分隔符字符连接该数组的值:
w = csv.writer(f)
在前面的示例中,由于我们没有使用分隔符选项,程序将使用默认字符逗号作为分隔符。如果您想使用空格作为分隔符字符,您可以使用以下 writer() 选项:
w = csv.writer(f, delimiter=' ')
要将列表的每个元素写入 writer 对象的新行,我们使用 writerow() 方法。
类似地,Python CSV 模块还提供了 reader() 函数来读取 CSV 文件。查看以下示例以了解更多关于此函数的信息,或者您可以从下一章的代码文件夹中打开 csvReader.py 文件:
import csv
with open('example.csv', 'r') as file:
r = csv.reader(file)
for row in r:
print row
reader() 函数创建一个 reader 对象来遍历打开的 CSV 文件中的行。reader 对象通过使用分隔符拆分行来检索每一行的每个元素。您可以通过使用 for 循环遍历对象来访问文件的每一行,如前述代码片段所示,或者每次想要访问下一行时使用 next() 方法。执行前面的代码后,您将能够看到三个分别打印出三个单独元素的单独数组列表。
小贴士
要在外部打开 CSV 文件,您可以使用电子表格程序,如 Microsoft Excel、OpenOffice Calc 或 Apple Numbers。
将 Arduino 数据存储在 CSV 文件中
在前面的两个部分中,您学习了将值存储在 CSV 文件中的方法。尽管文件所需的数据已经在代码中初始化,但相同的代码可以被修改以存储 Arduino 输入数据。
首先,让我们创建一个电路来为我们生成这些值以便存储 Arduino 数据。我们在第三章的项目中使用了运动传感器,第一个项目 – 运动触发 LED,以及在第四章的练习中使用了电位计,深入 Python-Arduino 原型设计。我们将使用这两个传感器分别提供数字和模拟输入值。为了开发这个练习所需的电路,将电位计连接到模拟引脚 0,将 PIR 运动传感器连接到数字引脚 11,如下面的图所示:

连接其他 Arduino 引脚,如 5V 和地,如图中所示的 Fritzing 图。由于我们将使用pyFirmata将 Python 与 Arduino 板接口,你将需要使用第三章中描述的方法将StandardFirmata草图上传到 Arduino 板。
注意
当你进行原型设计时,你实际上不需要大型、强大且计算密集型的数据库来处理信息。在这个阶段,处理传感器数据最简单、最快的方法是使用 CSV 文件。
一旦你的 Arduino 板准备好了适当的连接,使用以下代码片段创建一个 Python 文件并运行它。你也可以从这个章节的代码文件夹中打开csvArduinoStore.py文件:
import csv
import pyfirmata
from time import sleep
port = '/dev/cu.usbmodemfa1331'
board = pyfirmata.Arduino(port)
it = pyfirmata.util.Iterator(board)
it.start()
pirPin = board.get_pin('d:11:i')
a0 = board.get_pin('a:0:i')
with open('SensorDataStore.csv', 'w') as f:
w = csv.writer(f)
w.writerow(["Number", "Potentiometer", "Motion sensor"])
i = 0
pirData = pirPin.read()
potData = a0.read()
while i < 25:
sleep(1)
if pirData is not None:
i += 1
row = [i, potData, pirData]
w.writerow(row)
print "Done. CSV file is ready!"
board.exit()
当代码运行时,旋转电位计的旋钮,并在运动传感器前挥动手。这个动作将帮助你从这些传感器生成和测量不同的值。同时,程序将把数据记录在SensorDataStore.csv文件中。完成后,使用任何文本查看器或电子表格程序打开SensorDataStore.csv文件,你将能够看到存储在文件中的这些传感器值。现在,让我们尝试理解这个程序。
如你所见,代码中我们没有使用新的模块来接口 Arduino 板或存储传感器值到文件。相反,我们使用了之前练习中使用的相同方法。代码有两个不同的组件:Python-Arduino 接口和将数据存储到 CSV 文件。跳过对pyFirmata接口 Arduino 板方法的解释,让我们专注于与存储传感器数据相关的代码。我们将使用writerow()写入 CSV 文件的第一个行是标题行,它解释了列的内容:
w.writerow(["Number", "Potentiometer", "Motion sensor"])
然后,我们将从传感器获取读数并将它们写入 CSV 文件,如下面的代码片段所示。我们将重复这个过程 25 次,由变量i定义。你可以根据你的需求更改i的值。
while i < 25:
sleep(1)
if pirData is not None:
i += 1
row = [i, potData, pirData]
w.writerow(row)
接下来的问题是您如何将这个编码练习应用到您的自定义项目中?该程序有三个主要部分,可以根据项目需求进行定制,具体如下:
-
Arduino 引脚: 您可以更改 Arduino 引脚编号和要使用的引脚数量。您可以通过向行对象添加额外的传感器值来完成此操作。
-
CSV 文件: 文件名及其位置可以从
SensorDataStore.csv更改为适用于您应用程序的特定文件。 -
数据点的数量: 在运行
while循环 25 次时,我们收集了 25 组不同的数据点。您可以更改此值。您还可以更改连续点之间的时间延迟,从程序中使用的 1 秒更改为您需要的值。
开始使用 matplotlib
matplotlib库是最受欢迎且广泛支持的 Python 绘图库之一。尽管matplotlib受到 MATLAB 的启发,但它与 MATLAB 独立。与其他我们使用过的 Python 库类似,它是一个开源的 Python 库。matplotlib库通过简单易用的内置函数和方法,从简单的代码行中创建 2D 图表。matplotlib库在基于 Python 的应用程序中广泛用于数据可视化和分析。它使用NumPy(数值 Python 的简称)和SciPy(科学 Python 的简称)包进行数学计算分析。这些包是matplotlib的主要依赖项,包括freetype和pyparsing。如果您使用的是除下一节中提到的安装方法之外的任何其他安装方法,请确保您已预先安装了这些包。您可以从其官方网站(matplotlib.org/)获取有关matplotlib库的更多信息。
在 Windows 上配置 matplotlib
在我们在 Windows 上安装matplotlib之前,请确保您的 Windows 操作系统安装了最新的 Python 2.x 发行版。在第一章“Python 和 Arduino 入门”中,我们安装了 Setuptools 来下载和安装额外的 Python 包。请确保您已正确安装并配置了 Setuptools。在进一步操作之前,我们必须安装matplotlib的依赖项。打开命令提示符,并使用以下命令安装dateutil和pyparsing包:
> easy_install.exe python_dateutil
> easy_install.exe pyparsing
一旦成功安装了这些包,请从sourceforge.net/projects/numpy/下载并安装预编译的NumPy包。请确保您选择了适用于 Python 2.7 和您的 Windows 操作系统类型的适当安装文件。
现在,你的计算机应该已经满足了 matplotlib 的所有先决条件。从 matplotlib.org/downloads.html 下载并安装预编译的 matplotlib 包。
在这个安装过程中,我们为了避免与 Windows 操作系统中的 matplotlib 相关的一些已知问题,没有使用 Setuptools 来安装 NumPy 和 matplotlib。如果你能想出使用 Setuptools 安装这些包的方法,那么你可以跳过前面的手动步骤。
在 Mac OS X 上配置 matplotlib
根据 Mac OS X 的版本和依赖项的可用性,在 Mac OS X 上安装 matplotlib 可能会有困难。确保你已经按照 第一章 中描述的安装了 Setuptools,Python 和 Arduino 入门。假设你已经安装了 Setuptools 和 pip,请在终端运行以下命令:
$ sudo pip install matplotlib
执行此命令将导致以下三种可能性之一:
-
成功安装最新版本的
matplotlib -
通知显示要求已满足,但安装的版本比当前版本旧,目前为 1.3
-
安装
matplotlib包时出错
如果你遇到第一种可能性,那么你可以进入下一节;否则,遵循故障排除说明。你可以使用以下命令在 Python 交互式提示符中检查你的 matplotlib 版本:
>>> import matplotlib
>>> matplotlib.__version__
升级 matplotlib
如果你遇到第二种可能性,即现有的 matplotlib 版本比当前版本旧,请使用以下命令升级 matplotlib 包:
$ sudo pip install –-upgrade matplotlib
如果在升级过程中遇到错误,请继续阅读下一节。
故障排除安装错误
如果你通过 pip 安装 matplotlib 时遇到任何错误,最可能的原因是你缺少一些依赖包。依次遵循以下步骤来排除错误。
提示
每完成一步后,使用以下命令之一来检查错误是否已解决:
$ sudo pip install matplotlib
$ sudo pip install –-upgrade matplotlib
-
从 Apple 的 App Store 安装 Xcode。打开 Xcode 并导航到 首选项… 中的 下载 选项卡。从 首选项… 中下载并安装 命令行工具。这一步应该解决任何与编译相关的错误。
-
使用以下命令在终端安装
homebrew:$ ruby -e "$("$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)")" -
使用
homebrew安装以下包:$ brew install freetype $ brew install pkg-config如果你仍然收到
freetype包的错误,尝试使用以下命令为freetype创建链接:$ brew link freetype $ ln -s /usr/local/opt/freetype/include/freetype2 /usr/local/include/freetype如果在执行前面的步骤后收到任何进一步的错误,请访问
matplotlib.1069221.n5.nabble.com/上的matplotlib论坛以获取这些特定错误。注意
如果你在 Mac OS X 上使用
matplotlib,你需要设置适当的绘图后端,如下面的代码片段所示:import matplotlib matplotlib.use('TkAgg''')您可以在
matplotlib.org/faq/usage_faq.html#what-is-a-backend了解更多关于matplotlib绘图后端的信息。
在 Ubuntu 上设置 matplotlib
在 Ubuntu 上安装matplotlib及其所需依赖项是一个非常简单的过程。我们可以不使用 Setuptools,借助 Ubuntu 包管理器来完成此操作。以下简单的命令应该对您有所帮助:
$ sudo apt-get install python-matplotlib
当提示选择依赖项时,点击是以安装所有依赖项。您也应该能在其他流行的 Linux 发行版中找到matplotlib包。
使用 matplotlib 绘制随机数
matplotlib库通过pyplot框架提供了一系列基本的绘图相关函数和方法。pyplot框架包含创建图形、绘制图形、设置标题、设置坐标轴以及许多其他绘图方法。pyplot提供的一个重要函数是figure()。该函数初始化一个空的图形画布,可以用于您的绘图或一系列绘图:
fig1 = pyplot.figure(1)
您可以通过指定一个数字作为参数来创建多个图形,例如figure(2)。如果已经存在具有此数字的图形,该方法将激活现有的图形,然后可以进一步用于绘图。
matplotlib库提供了plot()方法来创建折线图。plot()方法接受一个由整数或浮点数组成的列表或数组数据结构作为输入。如果使用两个数组作为输入,plot()将它们用作x轴和y轴的值。如果只提供一个列表或数组,plot()假定它是y轴的序列值,并使用自动生成的增量值用于x轴:
pyplot.plot(x, y)
plot()方法支持的第三个可选参数是格式字符串。这些参数帮助用户使用不同的颜色更改线条和标记的样式。在我们的例子中,我们使用的是实线样式。因此,我们的绘图plot()函数看起来像这样:
pyplot.plot(x, y, '-')
plot()函数提供了一组大量样式和颜色的选择。要了解更多关于这些参数的信息,请在matplotlib的plot()函数上使用 Python 的help()函数:
>>> import matplotlib
>>> help(matplotlib.pyplot.plot)
这个help()函数将提供创建具有不同标记、线条样式和颜色的绘图样式的必要信息。您可以在提示符下输入q退出帮助菜单。
现在,我们已经充分探索了绘图,让我们使用以下代码片段创建您的第一个 Python 绘图。包含此代码的程序也位于本章代码文件夹中,文件名为plotBasic.py:
from matplotlib import pyplot
import random
x = range(0,25)
y = [random.randint(0,100) for r in range(0,25)]
fig1 = pyplot.figure()
pyplot.plot(x, y, '-')
pyplot.title('First Plot - Random integers')
pyplot.xlabel('X Axis')
pyplot.ylabel('Y Axis')
pyplot.show()
在前面的练习中,我们使用 randint() 方法随机生成了 y 轴的数据库。你可以看到在运行程序后打开的窗口中,用实线风格描绘了这些数据的图表。正如你在代码片段中看到的,我们使用了额外的 pyplot 方法,如 title()、xlabel()、ylabel() 和 plot()。这些方法都是自我解释的,并且它们主要用于使你的图表更加信息丰富和有意义。
在示例的末尾,我们使用了最重要的 pyplot 方法之一,即 show()。show() 方法在图中显示生成的图表。当从 Python 的交互式提示符运行时,此方法不是显示图表的强制要求。以下截图展示了使用 matplotlib 生成的随机数值的图表:

从 CSV 文件中绘制数据
在本章的开头,我们从 Arduino 数据创建了一个 CSV 文件。我们将使用该 SensorDataStore.csv 文件本节。如果你还记得,我们使用了两个不同的传感器来记录数据。因此,我们有两个值数组,一个来自数字传感器,另一个来自模拟传感器。现在,在前面的例子中,我们只是绘制了 y 轴的一组值。那么,我们如何分别并以有意义的方式绘制这两个数组呢?
让我们从创建一个新的 Python 程序开始,使用以下代码行或从本章的代码文件夹中打开 plotCSV.py 文件:
import csv
from matplotlib import pyplot
i = []
mValues = []
pValues = []
with open('SensorDataStore.csv', 'r') as f:
reader = csv.reader(f)
header = next(reader, None)
for row in reader:
i.append(int(row[0]))
pValues.append(float(row[1]))
if row[2] == 'True':
mValues.append(1)
else:
mValues.append(0)
pyplot.subplot(2, 1, 1)
pyplot.plot(i, pValues, '-')
pyplot.title('Line plot - ' + header[1])
pyplot.xlim([1, 25])
pyplot.xlabel('X Axis')
pyplot.ylabel('Y Axis')
pyplot.subplot(2, 1, 2)
pyplot.bar(i, mValues)
pyplot.title('Bar chart - ' + header[2])
pyplot.xlim([1, 25])
pyplot.xlabel('X Axis')
pyplot.ylabel('Y Axis')
pyplot.tight_layout()
pyplot.show()
在这个程序中,我们通过逐行读取 SensorDataStore.csv 文件创建了两个传感器值数组——pValues 和 mValues。在这里,pValues 和 mValues 分别代表电位计和运动传感器的传感器数据。一旦我们有了这两个列表,我们就使用 matplotlib 方法绘制了它们。
matplotlib 库提供了多种方式来绘制不同数组的值。你可以使用 figure() 方法分别在不同的两个图中绘制它们,即 figure(1) 和 figure(2),或者在一个图中绘制它们,使它们相互叠加。pyplot 方法还通过 subplot() 方法提供了一种第三种有意义的替代方案,允许在单个图中通过 subplot() 方法绘制多个图表:
pyplot.subplot(2,1,1)
这种方法的结构是 subplot(nrows, ncols, plot_number),它使用行号和列号(即 nrows 和 ncols)在图像画布上创建网格。此方法将图表放置在由 plot_number 参数提供的特定单元格中。例如,通过 subplot(2, 1, 1),我们创建了一个两行一列的表格,并将第一个子图放置在表格的第一个单元格中。同样,下一组值用于第二个子图,并放置在第二个单元格中,即第 2 行第 1 列:
pyplot.subplot(2, 1, 2)
在第一个子图中,我们使用了plot()方法来创建一个使用电位器的模拟值(即pValues)的图表。而在第二个子图中,我们创建了一个柱状图而不是折线图来显示运动传感器的数字值。柱状图功能是由bar()方法提供的。
如你在代码片段中看到的,我们使用了额外的pyplot()方法,称为xlim()。xlim([x_minimum, x_maximum])或ylim([y_minimum, y_maximum])方法用于将图表限制在特定轴的给定最大值和最小值之间。
在我们使用show()方法在图中显示这些子图之前,我们使用了tight_layout()函数来组织图中的标题和标签文本。tight_layout()函数是一个非常重要的matplotlib模块,它很好地将子图参数拟合到一个图中。你可以通过注释掉这一行并再次运行代码来检查这个模块的效果。以下截图显示了这些子图带有标签和标题在一个图对象中:

绘制实时 Arduino 数据
在前一章中,当处理 GUI 和 Arduino 数据时,你必须已经注意到代码会随着从 Arduino 传感器获得的新值更新界面。同样,在这个练习中,我们将每次从 Arduino 接收到新值时重绘图表。基本上,我们将绘制和更新一个实时图表,而不是像前一个练习那样绘制整个传感器值集。
我们将使用你在前一个练习中构建的相同的 Arduino 电路。在这里,我们将仅利用电路的电位器部分来获取模拟传感器的值。现在,在我们解释这个练习中使用的新的方法之前,让我们首先打开这个练习的程序文件。你可以从这个章节的文件夹中找到程序文件;它被命名为plotLive.py。在代码中,更改 Arduino 板的适当参数并执行代码。当代码运行时,旋转电位器的旋钮以观察图表中的实时变化。
运行程序后,你将看到一个类似于以下截图的屏幕,该截图显示了实时 Arduino 数据的图表。

通过观察图表,可以得出关于电位器旋钮旋转或其他传感器行为的各种结论。这类图表在图形仪表板中广泛应用于实时监控应用。现在,让我们尝试理解以下代码片段中使用的使这一切成为可能的方法。
import sys, csv
from matplotlib import pyplot
import pyfirmata
from time import sleep
import numpy as np
# Associate port and board with pyFirmata
port = '/dev/cu.usbmodemfa1321''
board = pyfirmata.Arduino(port)
# Using iterator thread to avoid buffer overflow
it = pyfirmata.util.Iterator(board)
it.start()
# Assign a role and variable to analog pin 0
a0 = board.get_pin(''a:0:i'')
# Initialize interactive mode
pyplot.ion()
pData = [0] * 25
fig = pyplot.figure()
pyplot.title(''Real-time Potentiometer reading'')
ax1 = pyplot.axes()
l1, = pyplot.plot(pData)
pyplot.ylim([0,1])
# real-time plotting loop
while True:
try:
sleep(1)
pData.append(float(a0.read()))
pyplot.ylim([0, 1])
del pData[0]
l1.set_xdata([i for i in xrange(25)])
l1.set_ydata(pData) # update the data
pyplot.draw() # update the plot
except KeyboardInterrupt:
board.exit()
break
本练习中的实时绘图是通过结合使用 pyplot 函数 ion()、draw()、set_xdata() 和 set_data() 实现的。ion() 方法初始化 pyplot 的交互模式。交互模式有助于动态更改图中图形的 x 和 y 值:
pyplot.ion()
一旦将交互模式设置为 True,只有在调用 draw() 方法时才会绘制图形。
就像之前的 Arduino 接口练习一样,在代码的开始部分,我们使用 pyFirmata 初始化 Arduino 板并设置引脚以获取传感器值。正如您在以下代码行中可以看到的,在设置 Arduino 板和 pyplot 交互模式后,我们使用一组空白数据,在我们的例子中是 0,初始化了图形:
pData = [0] * 25
这个用于 y 值的数组,pData,然后在 while 循环中用于追加传感器值。while 循环持续追加最新的值到这个数据数组,并使用更新后的 x 和 y 值重新绘制图形。在这个例子中,我们在数组的末尾追加新的传感器值,同时移除数组的第一个元素以限制数组的大小:
pData.append(float(a0.read()))
del pData[0]
set_xdata() 和 set_ydata() 方法用于从这些数组中更新 x 和 y 轴的数据。这些更新值在每个 while 循环迭代中使用 draw() 方法进行绘图:
l1.set_xdata([i for i in xrange(25)])
l1.set_ydata(pData) # update the data
pyplot.draw() # update the plot
您还会注意到我们正在使用 xrange() 函数根据提供的长度生成一系列值,在我们的例子中是 25。代码片段 [i for i in xrange(25)] 将生成一个包含 25 个整数数字的列表,这些数字从 0 开始递增,到 24 结束。
在 Tkinter 窗口中集成图形
由于 Python 强大的集成能力,将 matplotlib 库生成的图形与 Tkinter 图形界面集成非常方便。在前一章的最后一个练习中,我们将 Tkinter 与 pyFirmata 集成以实现 第三章 的项目,即 第一个项目 – 运动触发 LED,并使用 GUI。在这个练习中,我们将通过利用 matplotlib 进一步扩展这种集成。我们将通过利用本章中使用的相同 Arduino 电路和扩展之前练习中使用的代码来完成这个动作。同时,在这个练习中,我们不会引入任何新方法;相反,我们将利用到目前为止所学的内容。打开本章代码文件夹中的 plotTkinter.py 文件。
如前所述,程序利用了三个主要的 Python 库,并将它们相互连接以开发一个优秀的 Python-Arduino 应用程序。第一个接口点是在Tkinter和matplotlib之间。正如你在以下代码行中可以看到的,我们为开始、暂停和退出按钮分别初始化了三个按钮对象,即startButton、pauseButton和exitButton:
startButton = Tkinter.Button(top,
text="Start",
command=onStartButtonPress)
startButton.grid(column=1, row=2)
pauseButton = Tkinter.Button(top,
text="Pause",
command=onPauseButtonPress)
pauseButton.grid(column=2, row=2)
exitButton = Tkinter.Button(top,
text="Exit",
command=onExitButtonPress)
exitButton.grid(column=3, row=2)
开始和退出按钮提供了对matplotlib操作的控件点,例如通过各自的onStartButtonPress()和onExitButtonPress()函数更新图表和关闭图表。onStartButtonPress()函数还包含了matplotlib和pyFirmata库之间的接口点。正如你从下面的代码片段中可以观察到的,我们将使用draw()方法开始更新图表,并使用update()方法更新Tkinter窗口,对于从模拟引脚 a0 获得的每个观察值,该值是通过read()方法获得的:
def onStartButtonPress():
while True:
if flag.get():
sleep(1)
pData.append(float(a0.read()))
pyplot.ylim([0, 1])
del pData[0]
l1.set_xdata([i for i in xrange(25)])
l1.set_ydata(pData) # update the data
pyplot.draw() # update the plot
top.update()
else:
flag.set(True)
break
onExitButtonPress()函数实现了由其名称本身描述的exit功能。在断开 Arduino 板与串行端口连接之前,它关闭了pyplot图表和Tkinter窗口。
现在,在修改 Arduino 端口参数后执行程序。你应该能在屏幕上看到一个窗口,类似于以下截图所示。使用这段代码,你现在可以使用开始和暂停按钮控制实时图表。点击开始按钮并开始旋转电位计旋钮。当你点击暂停按钮时,你可以观察到程序已经停止绘制新的值。在暂停被按下时,即使旋转旋钮也不会导致图表有任何更新。
一旦你再次点击开始按钮,你将再次看到图表使用实时值更新,丢弃了暂停期间生成的值。点击退出按钮以安全关闭程序:

摘要
在本章中,我们介绍了两种主要的 Python 编程范式:使用 Python 创建、读取和写入文件,同时将这些文件中的数据存储起来,并在实时中绘制传感器值和更新图表。我们还探讨了存储和绘制实时 Arduino 传感器数据的方法。除了帮助你在 Arduino 项目中,这些方法也可以用于你的日常 Python 项目中。在整个章节中,通过简单的练习,我们将新学习的 CSV 和matplotlib模块与我们在前几章中学到的Tkinter和pyFirmata模块进行了接口。在下一章中,你将接触到你的第二个项目——一个便携式单元,它可以测量和显示环境数据,如温度、湿度和环境光线。我们将利用到目前为止学到的概念来构建这个项目。
第七章. 中期项目 – 可携带的 DIY 温度控制器
在完成第一个 Python-Arduino 项目之后,你学习了原型设计各种传感器、开发用户界面和绘制传感器数据的过程。你之前章节中学到的概念可以用来创建各种基于 Arduino 的硬件项目。一个好的应用程序概念的诞生总是始于现实世界的需求,如果执行得当,最终成为一个实用的项目。在本章中,我们将通过一个便携式传感器单元的例子来展示这个项目构建过程。正如你可以从章节标题中估计的那样,我们将构建一个简单且便携的 DIY 温度控制器,可以在没有台式计算机或笔记本电脑的情况下部署。
首先,我们将描述拟议的温度控制器,包括实现这些目标的具体目标和过程。一旦确定了实现这些目标的方法,你将介绍两个连续的编程阶段来开发可部署和可携带的单元。在第一阶段,我们将利用传统计算机成功开发程序,将 Arduino 与 Python 接口连接。在第二阶段,我们将用 Raspberry Pi 替换这台计算机,使其便携并可部署。
温度控制器 – 项目描述
在我们可以使用所学知识构建的多个项目中,一个帮助你监控周围环境的项⽬真正脱颖而出,成为一个重要的实际应用。从各种环境监控项目,如气象站、温度控制器和植物监控系统,我们将开发温度控制器,因为它专注于室内环境,可以成为你日常生活的部分。
温度控制器是任何远程家庭监控系统和家庭自动化系统中最重要组成部分之一。一个流行的商业温度控制器例子是 Nest 温度控制器(www.nest.com),它为现有家庭的供暖和冷却系统提供智能远程监控和调度功能。在我们考虑像 Nest 这样的全栈产品之前,我们首先需要构建一个具有基本功能的 DIY 温度控制器。之后,我们可以通过添加功能来改进 DIY 温度控制器的体验。让我们首先概述我们计划在本版温度控制器项目中实现的功能。
项目背景
温度、湿度和环境光线是我们希望通过温度控制器监控的三个主要物理特性。在用户体验方面,我们希望有一个优雅的用户界面来显示测量的传感器数据。如果任何传感器数据以折线图的形式绘制,用户体验将更加丰富。在温度控制器的例子中,传感器数据的可视化表示比仅仅显示纯数值更有意义。
该项目的重大目标之一是使恒温器便携和可部署,以便在日常生活中使用。为了满足这一要求,恒温器显示屏需要从常规显示器更改为更小、更便携的设备。为了确保其实际应用和意义,恒温器应展示实时操作。
重要的是要注意,恒温器将不会与任何执行器(如家用冷却和加热系统)接口。由于这些系统与恒温器项目的接口需要高级理解和与加热和冷却系统工作的经验,这将偏离章节原始目标,即教授你 Arduino 和 Python 编程。
项目目标和阶段
为了描述我们希望在恒温器中拥有的功能,让我们首先确定实现这些目标的目标和里程碑。项目的主要目标可以确定为以下:
-
确定项目所需的传感器和硬件组件
-
使用这些传感器和 Arduino 板设计并组装恒温器的电路
-
设计有效的用户体验并开发软件以适应用户体验
-
开发和实施代码以将设计的硬件与软件组件接口
恒温器项目的代码开发过程分为两个主要阶段。第一阶段的目标包括传感器接口、Arduino 脚本的开发以及开发你一直在使用的常规计算机上的 Python 代码。第一阶段编码里程碑可以进一步分配如下:
-
开发 Arduino 脚本以接口传感器和按钮,并通过串行端口将传感器数据输出到 Python 程序
-
开发 Python 代码,使用
pySerial库从串行端口获取传感器数据,并使用在Tkinter中设计的 GUI 显示数据 -
使用
matplotlib库创建一个图表来展示实时湿度读数
在第二阶段,我们将把 Arduino 硬件连接到单板计算机和微型显示屏,使其便携和可部署。实现第二阶段目标的里程碑如下:
-
安装和配置单板计算机 Raspberry Pi,以运行第一阶段中的 Python 代码
-
将微型屏幕与 Raspberry Pi 接口和配置
-
优化 GUI 和图表窗口以适应这个小屏幕的分辨率
在本节的下一小节中,你将了解到两个阶段的所需组件列表,随后是硬件电路设计和软件流程设计。这些阶段的编程练习将在本章的下一两个部分中解释。
所需组件列表
我们没有经过识别所需组件的过程,而是根据它们在之前练习中的使用情况、易用性和可用性,已经为这个项目选择了组件。您可以根据您构建项目时的可用性或对其他传感器的熟悉程度来替换这些组件。只需确保,如果这些新组件与我们使用的组件不兼容,您要处理好电路连接和代码的修改。
在原型设计的第一阶段,我们需要组件来开发恒温器单元的电子电路。正如我们之前提到的,我们将通过我们的单元测量温度、湿度和环境光线。我们已经学习了关于温度传感器 TMP102 和环境光线传感器 BH1750 的内容,这些内容在第四章 深入 Python-Arduino 原型设计 中有所介绍。我们将使用这些传感器来完成这个项目,同时使用湿度传感器 HIH-4030。项目将使用与之前章节相同的 Arduino Uno 板以及必要的电缆。我们还需要两个按钮来为单元提供手动输入。第一阶段所需组件的总结如下表所示:
| 组件(第一阶段) | 数量 | 网站 |
|---|---|---|
| Arduino Uno | 1 | www.sparkfun.com/products/11021 |
| Arduino 用 USB 电缆 | 1 | www.sparkfun.com/products/512 |
| 面包板 | 1 | www.sparkfun.com/products/9567 |
| TMP102 温度传感器 | 1 | www.sparkfun.com/products/11931 |
| HIH-4030 湿度传感器 | 1 | www.sparkfun.com/products/9569 |
| BH1750 环境光线传感器 | 1 | www.robotshop.com/en/dfrobot-light-sensor-bh1750.html |
| 推按钮开关 | 2 | www.sparkfun.com/products/97 |
| 1 千欧姆电阻 | 2 | |
| 10 千欧姆电阻 | 2 | |
| 连接线 | 如需求数量 |
尽管表格提供了少数特定网站的链接,但您可以从您偏好的供应商那里获取这些组件。我们之前未使用过的两个主要组件 HIH-4030 湿度传感器和推按钮开关如下所述:
-
HIH-4030 湿度传感器:该传感器测量并提供相对湿度结果作为模拟输出。传感器的输出可以直接连接到 Arduino 的任何模拟引脚。以下图片展示了 SparkFun Electronics 销售的带有 HIH-4030 传感器的分线板。您可以从其数据表中了解更多关于 HIH-4030 传感器的信息,数据表可以从
www.sparkfun.com/datasheets/Sensors/Weather/SEN-09569-HIH-4030-datasheet.pdf获取:![所需组件列表]()
-
按钮开关:按钮开关是小型开关,可以在面包板上使用。按下时,开关输出状态变为高电平,否则为低电平。
![所需组件列表]()
在第二阶段,我们将通过用 Raspberry Pi 替换电脑来使传感器单元变得便携。为此,您需要以下组件开始:
| 组件(第二阶段) | 数量 | 图片 |
|---|---|---|
| Raspberry Pi | 1 | www.sparkfun.com/products/11546 |
| 带电源适配器的 Micro USB 线 | 1 | www.amazon.com/CanaKit-Raspberry-Supply-Adapter-Charger/dp/B00GF9T3I0/ |
| 8 GB SD 卡 | 1 | www.sparkfun.com/products/12998 |
| TFT 液晶显示屏 | 1 | www.amazon.com/gp/product/B00GASHVDU/ |
| USB 集线器 | 可选 |
本章后面将提供这些组件的进一步说明。
硬件设计
恒温器的整个硬件架构可以分为两个单元:物理世界接口单元和计算单元。物理世界接口单元,正如其名称所示,通过连接到 Arduino 板上的传感器监测物理世界现象,如温度、湿度和环境光线。物理世界接口单元在整章中交替提到恒温器传感器单元。计算单元负责通过 GUI 和图表显示传感器信息。
以下图表展示了第一阶段硬件组件,其中恒温器传感器单元通过 USB 端口连接到电脑。在恒温器传感器单元中,各种传感器组件通过 I2C、模拟和数字引脚连接到 Arduino 板:

在第二个编程阶段,我们将把我们的恒温器变成一个可移动和可部署的单元,您将使用单板计算机 Raspberry Pi 作为计算设备。在这个阶段,我们将使用一个连接到 Raspberry Pi 的微型薄膜晶体管液晶显示屏(TFT LCD),它通过通用输入/输出(GPIO)引脚连接,并用作显示单元,以替代传统的显示器或笔记本电脑屏幕。以下图显示了新的恒温器计算单元,它真正减小了恒温器的整体尺寸,使其便携和移动。在这个阶段,Arduino 板的电路连接保持不变,我们将使用相同的硬件,无需进行任何重大修改。

作为项目两个阶段的共同单元,以 Arduino 为中心的恒温器传感器单元与其他练习相比,需要更复杂的电路连接。在本节中,我们将将必要的传感器和按钮接口到 Arduino 板的相应引脚上,您需要使用面包板来建立这些连接。如果您熟悉 PCB 原型设计,您可以通过焊接这些组件来创建自己的 PCB 板,从而避免使用面包板。与面包板相比,PCB 板更坚固,且不太容易发生松动连接。请使用以下说明和 Fritzing 图表来完成电路连接:
-
如下图中所示,将 TMP102 和 BH1750 的 SDA 和 SCL 引脚连接到 Arduino 板的模拟引脚 4 和 5,并创建一个 I2C 总线。为了进行这些连接,您可以使用多色编码的电线来简化调试过程。
-
使用两个 10 千欧姆的上拉电阻连接 SDA 和 SCL 线。
-
与这些 I2C 传感器相反,HIH-4030 湿度传感器是一个简单的模拟传感器,可以直接连接到模拟引脚。将 HIH-4030 连接到模拟引脚 A0。
-
使用面包板的电源条,将 TMP102、BH1750 和 HIH-4030 的 VCC 和地连接到 Arduino 板的 +5V 和地,如图所示。我们建议您使用红色和黑色电线分别代表 +5V 和地线。
-
按钮开关提供高或低状态的输出,并通过数字引脚进行接口。如图电路所示,使用两个 1 千欧姆电阻将按钮开关连接到数字引脚 2 和 3。
-
按照以下图中的显示完成剩余的连接。在给 Arduino 板通电之前,请确保所有电线都已牢固连接:
![硬件设计]()
注意
在进行任何连接之前,请确保您始终断开 Arduino 板的电源或 USB 端口。这将防止由于短路而损坏板子。
在前往下一节之前,完成恒温器传感器单元的所有连接。由于该单元在编程阶段都会使用,你不会对恒温器传感器单元进行任何进一步的更改。
软件流程用于用户体验设计
任何项目的关键组成部分之一是其可用性或可访问性。当你正在将你的项目原型转化为产品时,有必要拥有一个直观且资源丰富的用户界面,以便用户可以轻松地与你的产品交互。因此,在开始编码之前,有必要定义项目的用户体验和软件流程。软件流程包括流程图和程序的逻辑组件,这些组件是从项目需求中推导出来的。根据我们为恒温器项目定义的目标,软件流程可以在以下图中展示:

在实施过程中,项目的软件流程首先通过 Arduino 测量温度、湿度和环境光线,并逐行打印在串行端口上。Python 程序通过串行端口从 Arduino 获取传感器数据,然后在屏幕上显示数据。同时,Python 程序持续寻找新的数据行。
用户可以使用按钮与恒温器进行交互,这将允许用户更改温度数据的单位。一旦按钮被按下,标志位变为HIGH,温度单位从默认单位华氏度更改为摄氏度。如果再次按下按钮,将发生相反的过程,单位将恢复到默认值。同样,另一个用户交互点是第二个按钮,允许用户打开实时湿度值的图表。第二个按钮也使用类似的方法使用标志位捕获输入并打开新的图表窗口。如果连续按下相同的按钮,程序将关闭图表窗口。
第 1 阶段 – 恒温器的原型设计
在这个原型设计阶段,我们将为我们的恒温器开发 Arduino 和 Python 代码,这些代码将在第二阶段进行少量更改后使用。在开始编码练习之前,请确保你有恒温器传感器单元准备就绪,包括 Arduino 板和连接的传感器,如前所述。对于这个阶段,你将使用配备 Arduino IDE 和 Python 编程环境的常规计算机。原型设计阶段需要两个级别的编程,即恒温器传感器单元的 Arduino 草图和计算单元的 Python 代码。让我们开始为我们的恒温器编写代码。
恒温器的 Arduino 草图
这个 Arduino 程序的目标是连接传感器,从传感器获取测量值,并在串行端口上打印它们。正如我们之前讨论的,我们不会使用之前项目中使用的标准 Firmata 草图,而是在这个项目中开发一个自定义的 Arduino 草图。要开始,打开本章代码文件夹中的Thermostat_Arduino.ino草图,这是您为本书收到的源代码的一部分。
将 Arduino 板的 USB 端口连接到您的计算机。在 Arduino IDE 中选择适当的板和端口名称,并验证代码。一旦代码成功上传,打开串行监视器窗口。您应该能够看到类似于以下截图显示的文本:

Arduino 代码结构和基本声明已在本书的各个部分中解释过。我们不会逐行解释整个代码,而是将重点放在我们之前描述的软件流程的主要组件上。
连接温度传感器
在 Arduino 草图中,使用getTemperature()函数从 TMP102 传感器获取温度数据。该函数在 TMP102 的 I2C 地址上实现了Wire库来读取传感器数据。然后将其转换为适当的温度值:
float getTemperature(){
Wire.requestFrom(tmp102Address, 2);
byte MSB = Wire.read();
byte LSB = Wire.read();
//it's a 12bit int, using two's compliment for negative
int TemperatureSum = ((MSB << 8) | LSB) >> 4;
float celsius = TemperatureSum*0.0625;
return celsius;
}
getTemperature()函数返回摄氏度的温度值,然后将其发送到串行端口。
连接湿度传感器
虽然湿度传感器提供模拟输出,但由于它还取决于温度,因此直接获取相对湿度并不简单。getHumidity()函数从 HIH-4030 传感器提供的模拟输出计算相对湿度。计算相对湿度的公式来自数据表和传感器的参考示例。如果您使用的是不同的湿度传感器,请确保相应地更改公式,因为它们可能会显著改变结果:
float getHumidity(float degreesCelsius){
//caculate relative humidity
float supplyVolt = 5.0;
// Get the sensor value:
int HIH4030_Value = analogRead(HIH4030_Pin);
// convert to voltage value
float voltage = HIH4030_Value/1023\. * supplyVolt;
// convert the voltage to a relative humidity
float sensorRH = 161.0 * voltage / supplyVolt - 25.8;
float trueRH = sensorRH / (1.0546 - 0.0026 * degreesCelsius);
return trueRH;
}
由于我们正在计算相对湿度,因此返回的湿度值以百分比为单位发送到串行端口。
连接光传感器
要连接 BH1750 光传感器,我们将使用之前使用过的 BH1750 Arduino 库。使用此库,可以使用以下代码行直接获取环境光值:
uint16_t lux = lightMeter.readLightLevel();
这行提供了以lux为单位的光亮度值。这些值也会发送到串行端口,以便 Python 程序可以进一步利用它。
使用 Arduino 中断
到目前为止,你使用 Arduino 程序通过DigitalRead()或AnalogRead()函数读取 I/O 引脚的物理状态。你是如何自动获取状态变化,而不是定期读取引脚并等待状态变化呢?Arduino 中断为 Arduino 板提供了非常方便的捕获信号的方法。中断是自动控制 Arduino 中各种事物的一种非常强大的方式。Arduino 使用attachInterrupt()方法支持中断。就物理引脚而言,Arduino Uno 提供了两个中断:中断 0(在数字引脚 2 上)和中断 1(在数字引脚 3 上)。各种 Arduino 板对中断引脚有不同的规格。如果你使用的是除 Uno 以外的任何板,请参考 Arduino 网站以了解你板的中断引脚。
attachInterrupt()函数接受三个输入参数(pin、ISR和mode)。在这些输入参数中,pin指的是中断引脚的编号,ISR(代表中断服务例程)指的是当中断发生时被调用的函数,而mode定义了中断应该被触发时的条件。我们已经在我们描述的以下代码片段中使用了这个函数:
attachInterrupt(0, button1Press, RISING);
attachInterrupt(1, button2Press, RISING);
attachInterrupt()函数支持的mode有LOW、CHANGE、RISING和FALLING。在我们的案例中,当模式为RISING时,即引脚从低电平变为高电平,中断被触发。对于声明在 0 和 1 的中断,我们调用button1Press和button2Press函数,分别改变flagTemperature和flagPlot。当flagTemperature设置为HIGH时,Arduino 发送摄氏温度,否则发送华氏温度。当flagPlot为HIGH时,Arduino 将在串行端口上打印标志,该标志将被 Python 程序稍后用于打开绘图窗口。你可以从arduino.cc/en/Reference/attachInterrupt上的教程中了解更多关于 Arduino 中断的信息。
在 Python 中设计 GUI 和绘图
一旦你的恒温器传感器单元开始向串行端口发送传感器数据,就到了执行这个阶段的第二部分的时候了,即 GUI 和绘图用的 Python 代码。从本章的代码文件夹中,打开名为Thermostat_Stage1.py的 Python 文件。在文件中,找到包含Serial()函数的行,该函数声明了串行端口。将串行端口名称从COM5更改为适当的名称。你可以从 Arduino IDE 中找到这个信息。保存更改并退出编辑器。从同一文件夹中,在终端运行以下命令:
$ python Thermostat_Stage1.py
这将执行 Python 代码,你将能够在屏幕上看到 GUI 窗口。
在你的 Python 程序中使用 pySerial 流式传输传感器数据
如软件流程所述,程序使用pySerial库从 Arduino 接收传感器数据。在 Python 代码中声明串行端口的代码如下:
Import serial
port = serial.Serial('COM5',9600, timeout=1)
在使用pySerial库时,指定timeout参数非常重要,因为如果没有指定timeout,代码可能会出错。
使用 Tkinter 设计 GUI
本项目的 GUI 设计使用了之前我们使用的Tkinter库。作为一个 GUI 构建练习,程序中编写了三列标签(用于显示传感器类型、观测值和观测单位),如下代码片段所示:
# Labels for sensor name
Tkinter.Label(top, text = "Temperature").grid(column = 1, row = 1)
Tkinter.Label(top, text = "Humidity").grid(column = 1, row = 2)
Tkinter.Label(top, text = "Light").grid(column = 1, row = 3)
# Labels for observation values
TempLabel = Tkinter.Label(top, text = " ")
TempLabel.grid(column = 2, row = 1)
HumdLabel = Tkinter.Label(top, text = " ")
HumdLabel.grid(column = 2, row = 2)
LighLabel = Tkinter.Label(top, text = " ")
LighLabel.grid(column = 2, row = 3)
# Labels for observation unit
TempUnitLabel = Tkinter.Label(top, text = " ")
TempUnitLabel.grid(column = 3, row = 1)
HumdUnitLabel = Tkinter.Label(top, text = "%")
HumdUnitLabel.grid(column = 3, row = 2)
LighUnitLabel = Tkinter.Label(top, text = "lx")
LighUnitLabel.grid(column = 3, row = 3)
在初始化代码并点击开始按钮之前,你将能够看到以下窗口。在此阶段,观测标签被填充,但没有任何值:

点击开始按钮后,程序将激活恒温传感器单元并开始从串行端口读取传感器值。使用从串行端口获得的行,程序将用获取的值填充观测标签。以下代码片段更新了观测标签中的温度值,并更新了温度单位:
TempLabel.config(text = cleanText(reading[1]))
TempUnitLabel.config(text = "C")
TempUnitLabel.update_idletasks()
在程序中,我们使用类似的方法来更新湿度和周围光线标签的值。正如你在以下截图中所看到的,GUI 现在有了温度、湿度和周围光线读数的值:

开始和退出按钮被编程为当用户点击时调用onStartButtonPress()和onExitButtonPress()函数。当用户点击时,onStartButtonPress()函数执行创建用户界面所需的代码,而onExitButtonPress()函数关闭所有打开的窗口,断开恒温传感器单元的连接,并退出代码:
StartButton = Tkinter.Button(top,
text="Start",
command=onStartButtonPress)
StartButton.grid(column=1, row=4)
ExitButton = Tkinter.Button(top,
text="Exit",
command=onExitButtonPress)
ExitButton.grid(column=2, row=4)
你可以通过开始和退出按钮来探索 Python 代码。要观察传感器读数的变化,尝试吹气或在一个恒温传感器单元上放置障碍物。如果程序表现不当,请检查终端以查找错误信息。
使用 matplotlib 绘制湿度百分比
我们将使用matplotlib库实时绘制相对湿度值。在本项目中,我们将绘制相对湿度值,因为数据范围固定在 0 到 100 百分比之间。使用类似的方法,你也可以绘制温度和周围光线传感器的值。在开发绘制温度和周围光线传感器数据的代码时,确保你使用适当的范围来覆盖同一图表中的传感器数据。现在,正如我们在onStartButtonPress()函数中指定的,当你按下绘图按钮时,将弹出一个类似于以下截图的窗口:

以下代码片段负责使用湿度传感器的值绘制折线图。这些值在 y 轴上限制在 0 到 100 之间,其中 y 轴表示相对湿度范围。每当程序接收到新的湿度值时,图表就会更新:
pyplot.figure()
pyplot.title('Humidity')
ax1 = pyplot.axes()
l1, = pyplot.plot(pData)
pyplot.ylim([0,100])
使用按钮中断来控制参数
按钮中断是用户体验的关键部分,因为用户可以使用这些中断来控制温度单位和图表。使用按钮中断实现的 Python 功能如下。
通过按按钮更改温度单位
Arduino 脚本包含处理按钮中断的逻辑,并使用它们来更改温度单位。当发生中断时,它不是打印华氏度温度,而是将摄氏度温度发送到串行端口。如以下截图所示,Python 代码只是打印获得的温度观测值的数值及其相关的单位:

如以下代码片段所示,如果 Python 代码接收到 Temperature(C) 字符串,它将打印摄氏度温度,如果接收到 Temperature(F) 字符串,它将打印华氏度温度:
if (reading[0] == "Temperature(C)"):
TempLabel.config(text=cleanText(reading[1]))
TempUnitLabel.config(text="C")
TempUnitLabel.update_idletasks()
if (reading[0] == "Temperature(F)"):
TempLabel.config(text=cleanText(reading[1]))
TempUnitLabel.config(text="F")
TempUnitLabel.update_idletasks()
通过按按钮在 GUI 和图表之间切换
如果 Python 代码从串行端口接收到标志值的 1(高电平),它将创建一个新的图表并将湿度值绘制为折线图。然而,如果它接收到 0(低电平)作为标志值的,它将关闭任何打开的图表。如以下代码片段所示,程序将始终尝试使用最新的湿度读数更新图表。如果程序找不到打开的图表来绘制此值,它将创建一个新的图表:
if (reading[0] == "Flag"):
print reading[1]
if (int(reading[1]) == 1):
try:
l1.set_xdata(np.arange(len(pData)))
l1.set_ydata(pData) # update the data
pyplot.ylim([0, 100])
pyplot.draw() # update the plot
except:
pyplot.figure()
pyplot.title('Humidity')
ax1 = pyplot.axes()
l1, = pyplot.plot(pData)
pyplot.ylim([0, 100])
if (int(reading[1]) == 0):
try:
pyplot.close('all')
l1 = None
except:
到目前为止,你应该对恒温器传感器单元和计算单元所需的程序有一个完整的了解。由于涉及到的复杂性,你可能在执行这些程序时遇到一些已知的问题。如果你遇到任何麻烦,可以参考 故障排除 部分。
故障排除
这里有一些你可能遇到的错误及其修复方法:
-
I2C 传感器返回错误字符串:
-
检查 SDA 和 SCL 引脚的连接。
-
确保你在传感器的读数周期之间提供足够的延迟。检查数据表中的延迟和消息序列。
-
-
按钮按下时,图表窗口闪烁而不是保持显示:
-
不要多次尝试按它。握住并快速释放。确保你的按钮连接正确。
-
调整 Arduino 脚本中的延迟。
-
第二阶段 – 使用 Raspberry Pi 部署可用的恒温器
我们现在已经创建了一个恒温器,它作为一个 Arduino 原型存在,同时 Python 程序从您的电脑上运行。由于连接的电脑和如果您使用台式电脑时的显示器,这个原型仍然离可部署或便携状态相去甚远。一个现实世界的恒温器设备应该具有小巧的尺寸、便携的体积和微型显示屏来显示有限的信息。实现这一目标的流行且实用的方法是使用一个能够运行操作系统并提供基本 Python 编程接口的小型单板计算机。对于这个项目阶段,我们将使用一个单板计算机——树莓派——它配备了一个小型的 LCD 显示屏。
注意
注意,除非您想将项目扩展到日常可用的设备,否则这个项目阶段是可选的。如果您只是想学习 Python 编程,您可以跳过这一整个部分。
下面的图片是树莓派 Model B:

如果您之前没有使用过单板计算机,您可能会有很多未解答的问题,例如“树莓派究竟由什么组成?”、“在我们的项目中使用树莓派有什么好处?”以及“我们不能只用 Arduino 吗?”这些问题都是合理的,我们将在下一节中尝试回答它们。
什么是树莓派?
树莓派是一款小型(几乎与信用卡大小相当)的单板计算机,最初旨在帮助学生学习计算机科学的基础知识。如今,在树莓派基金会的指导下,树莓派运动已经变成了一种 DIY 现象,吸引了全球爱好者和开发者的关注。树莓派以低廉的成本(35 美元)提供的功能和特性,极大地提升了该设备的人气。
单板计算机这个术语用于指代那些在一个板上集成所有运行操作系统所需组件的设备,例如处理器、RAM、图形处理器、存储设备和基本的扩展适配器。这使得单板计算机成为便携式应用的合适候选者,因为它们可以成为我们试图创建的便携式硬件设备的一部分。尽管在树莓派推出之前市场上已经存在许多单板计算机,但硬件的开放源代码性质和经济价格是树莓派流行和快速采用的主要原因。以下图显示了树莓派 Model B 及其主要组件:

Raspberry Pi 的计算能力足以运行 Linux OS 的精简版。尽管人们尝试在 Raspberry Pi 上使用许多类型的操作系统,但我们将使用默认和推荐的操作系统,称为Raspbian。Raspbian 是基于 Debian 发行版的开源 Linux 操作系统,针对 Raspberry Pi 进行了优化。Raspberry Pi 使用 SD 卡作为存储设备,将用于存储您的操作系统和程序文件。在 Raspbian 中,您可以避免运行传统操作系统附带的不必要组件。这些包括网络浏览器、通信应用程序,在某些情况下甚至包括图形界面。
在其推出后,Raspberry Pi 经历了几次重大升级。早期版本称为Model A,不包括以太网端口,只有 256 MB 的内存。在我们的项目中,我们使用的是带有专用以太网端口、512 MB 内存和双 USB 端口的 Raspberry Pi Model B。最新的 Raspberry Pi 版本 Model B+也可以使用,因为它也配备了以太网端口。
安装操作系统和配置 Raspberry Pi
虽然 Raspberry Pi 是一台计算机,但在连接外围设备方面与传统台式计算机不同。Raspberry Pi 不支持传统的 VGA 或 DVI 显示端口,而是为电视提供 RCA 视频端口,为最新一代的显示器和电视提供 HDMI 端口。此外,Raspberry Pi 只有两个 USB 端口,需要用于连接各种外围设备,如鼠标、键盘、USB 无线适配器和 USB 闪存盘。让我们开始收集组件和电缆,以便开始使用 Raspberry Pi。
您需要什么来开始使用 Raspberry Pi?
开始使用 Raspberry Pi 所需的硬件组件如下:
-
Raspberry Pi:在这个项目阶段,您需要一个版本为 Model B 或最新的 Raspberry Pi。您可以从
www.raspberrypi.org/buy购买 Raspberry Pi。 -
电源线:Raspberry Pi 使用 5V 直流电,至少需要 750 mA 的电流。电源通过位于板上的微型 USB 端口施加。在这个项目中,您需要一个微型 USB 电源。可选地,您可以使用基于微型 USB 的手机充电器为 Raspberry Pi 供电。
-
显示器连接线:如果您有一台 HDMI 显示器或电视,您可以使用 HDMI 线将其连接到您的 Raspberry Pi。如果您想使用基于 VGA 或 DVI 的显示器,您将需要一个 VGA 到 HDMI 或 DVI 到 HDMI 适配器转换器。您可以从 Amazon 或 Best Buy 购买这些适配器转换器。
-
SD 卡:您至少需要一个 8GB 的 SD 卡才能开始。最好使用质量为 4 级或更好的 SD 卡。您还可以在
swag.raspberrypi.org/collections/frontpage/products/noobs-8gb-sd-card购买预装操作系统的 SD 卡。注意
Raspberry Pi Model B+需要 microSD 卡而不是常规 SD 卡。
-
鼠标和键盘:您将需要一个标准的 USB 键盘和一个 USB 鼠标来与 Raspberry Pi 一起工作。
-
USB 集线器(可选):由于 Model B 只有两个 USB 端口,如果您想连接 Wi-Fi 适配器或内存棒,您将不得不从 USB 端口移除现有设备以腾出空间。USB 集线器可以方便地将多个外围组件连接到您的 Raspberry Pi 上。我们建议您使用带外部电源的 USB 集线器,因为由于电源限制,Raspberry Pi 只能通过 USB 端口驱动有限数量的外围设备。
准备 SD 卡
要安装和配置如 Python 和所需库等软件组件,首先我们需要为 Raspberry Pi 提供一个操作系统。Raspberry Pi 官方支持基于 Linux 的开源操作系统,这些操作系统预先配置了针对定制 Raspberry Pi 硬件组件。这些操作系统的各种版本可在 Raspberry Pi 的网站上找到(www.raspberrypi.org/downloads)。
Raspberry Pi 的网站为从新手到专家的各种用户提供了各种操作系统。对于初学者来说,很难识别合适的操作系统及其安装过程。如果您是第一次尝试使用 Raspberry Pi,我们建议您使用新开箱即用软件(NOOBS)包。从之前的链接下载NOOBS的最新版本。NOOBS包包括几个不同的操作系统,如 Raspbian、Pidora、Archlinux 和 RaspBMC。NOOBS简化了整个安装过程,并帮助您轻松安装和配置您首选的操作系统版本。需要注意的是,NOOBS只是一个安装包,一旦完成给定的安装步骤,您将只剩下 Raspbian 操作系统。
Raspberry Pi 使用 SD 卡来托管操作系统,您需要在将 SD 卡放入 Raspberry Pi 的 SD 卡槽之前,从您的电脑上准备 SD 卡。将您的 SD 卡插入电脑,并确保您备份了 SD 卡上任何重要的信息。在安装过程中,您将丢失 SD 卡上存储的所有数据。让我们先从准备您的 SD 卡开始。
按照以下步骤从 Windows 准备 SD 卡:
-
你需要一个软件工具来格式化和准备 SD 卡以供 Windows 使用。你可以从
www.sdcard.org/downloads/formatter_4/eula_windows/下载免费提供的格式化工具。 -
在你的 Windows 计算机上下载并安装格式化工具。
-
插入你的 SD 卡并启动格式化工具。
-
在格式化工具中,打开选项菜单并将格式大小调整设置为开启。
-
选择合适的 SD 卡并点击格式化。
-
然后,等待格式化工具完成格式化 SD 卡。一旦完成,将下载的
NOOBSZIP 文件提取到 SD 卡上。确保你将 ZIP 文件夹的内容提取到 SD 卡的根目录。
按照以下指示从 Mac OS X 准备 SD 卡:
-
你需要一个软件工具来格式化和准备 SD 卡以供 Mac OS X 使用。你可以从
www.sdcard.org/downloads/formatter_4/eula_mac/下载免费提供的格式化工具。 -
在你的机器上下载并安装格式化工具。
-
插入你的 SD 卡并运行格式化工具。
-
在格式化工具中,选择覆盖格式。
-
选择合适的 SD 卡并点击格式化。
-
然后,等待格式化工具完成格式化 SD 卡。一旦完成,将下载的
NOOBSZIP 文件提取到 SD 卡上。确保你将 ZIP 文件夹的内容提取到 SD 卡的根目录。
按照以下步骤从 Ubuntu Linux 准备 SD 卡:
-
要在 Ubuntu 上格式化 SD 卡,你可以使用一个名为
gparted的格式化工具。在终端中使用以下命令安装gparted:$ sudo apt-get install gparted -
插入你的 SD 卡并运行
gparted。 -
在
gparted窗口中,选择整个 SD 卡并使用FAT32格式化它。 -
一旦格式化过程完成,将下载的
NOOBSZIP 文件提取到 SD 卡上。确保你将 ZIP 文件夹的内容提取到 SD 卡的根目录。小贴士
如果你在这几个步骤中遇到任何问题,你可以参考
www.raspberrypi.org/documentation/installation/installing-images/中为 Raspberry Pi 准备 SD 卡的官方文档。
Raspberry Pi 的设置过程
一旦你用NOOBS准备好了你的 SD 卡,将其插入 Raspberry Pi 的 SD 卡槽中。在连接电源适配器的微型 USB 线之前,先连接你的显示器、鼠标和键盘。一旦连接了电源适配器,Raspberry Pi 将自动开机,你将能在显示器上看到安装过程。如果你在连接电源适配器后无法在显示器上看到任何进度,请参考本章后面可用的故障排除部分。
一旦 Raspberry Pi 启动,它将重新分区 SD 卡并显示以下安装屏幕,以便您开始使用:

注意
上述截图由 Simon Monk 从raspberry_pi_F01_02_5a.jpg中获取,并授权于 Attribution Creative Commons 许可(learn.adafruit.com/assets/11384)。
-
作为首次用户,请选择Raspbian [推荐]作为推荐的操作系统,并点击安装 OS按钮。Raspbian 是一个基于 Debian 的操作系统,针对 Raspberry Pi 进行了优化,并支持我们在前几章中学到的有用 Linux 命令。整个过程大约需要 10 到 20 分钟才能完成。
-
成功完成后,您将能够看到类似于以下截图的屏幕。该截图显示了
raspi-config工具,它将允许您设置初始参数。我们将跳过此过程以完成安装。选择<完成>并按Enter:![Raspberry Pi 设置过程]()
-
如果您想更改任何参数,可以再次回到这个屏幕,在终端中输入以下命令:
$ sudo raspi-config -
现在,Raspberry Pi 将重新启动,您将被提示默认登录屏幕。使用默认用户名
pi和密码raspberry登录。 -
您可以通过在终端中输入以下命令来启动 Raspberry Pi 的图形桌面:
$ startx -
要运行我们在第一阶段开发的 Python 代码,您需要在 Raspberry Pi 上设置所需的 Python 库。您需要使用以太网线将 Raspberry Pi 连接到互联网以安装包。使用以下命令在 Raspberry Pi 终端上安装所需的 Python 包:
$ sudo apt-get install python-setuptools, python-matplotlib, python-numpy -
使用 Setuptools 安装
pySerial:$ sudo easy_install pyserial
现在,您的 Raspberry Pi 已经准备好操作系统和必要的组件来支持 Python-Arduino 编程。
使用 Raspberry Pi 和便携式 TFT LCD 显示器
TFT LCD 是扩展 Raspberry Pi 功能并避免使用大型显示设备的好方法。这些 TFT LCD 显示器可以直接与 GPIO 引脚接口。TFT LCD 屏幕有各种形状和尺寸,但鉴于接口方便,我们建议您使用小于或等于 3.2 英寸的屏幕。大多数这些小型屏幕不需要额外的电源供应,可以直接使用 GPIO 引脚供电。在少数情况下,也提供触摸屏版本,以扩展 Raspberry Pi 的功能。
在本项目中,我们使用的是一款可直接通过 GPIO 与 Raspberry Pi 接口的 Tontec 2.4 英寸 TFT LCD 屏幕。虽然您可以使用任何可用的 TFT LCD 屏幕,但本书仅涵盖此特定屏幕的设置和配置过程。在大多数情况下,这些屏幕的制造商在其网站上提供了详细的配置教程。如果您使用的是不同类型的 TFT LCD 屏幕,Raspberry Pi 论坛和博客是寻找帮助的另一个好地方。以下图片显示了 Tontec 2.4 英寸 TFT LCD 屏幕的背面,以及 GPIO 引脚的位置。让我们开始使用这款屏幕与您的 Raspberry Pi 一起工作:

使用 GPIO 连接 TFT LCD
在我们能够使用屏幕之前,我们必须将其连接到 Raspberry Pi。让我们从 Raspberry Pi 上断开微型 USB 电源适配器,并找到位于 Raspberry Pi RCA 视频端口附近的 GPIO 阳性引脚。取下您的 TFT 屏幕,并按照以下图片所示连接 GPIO 引脚。在少数情况下,屏幕上的标记可能会误导,因此我们建议您遵循制造商的指南进行连接:

当您的屏幕连接到 Raspberry Pi 后,使用微型 USB 线缆为其供电。请勿断开 HDMI 线缆,因为您的屏幕尚未准备好。在我们进行任何配置步骤之前,让我们首先将 Raspberry Pi 连接到互联网。使用以太网线缆将 Raspberry Pi 的以太网端口连接到您的家庭或办公室网络。现在,让我们在 Raspbian OS 中配置 TFT LCD 屏幕,使其正常工作。
使用 Raspberry Pi OS 配置 TFT LCD
当您的 Raspberry Pi 启动后,使用您的用户名和密码登录。完成以下步骤以使用 Raspberry Pi 配置屏幕:
-
使用以下命令在终端下载支持文件和手册:
$ wget https://s3.amazonaws.com/tontec/24usingmanual.zip -
解压文件。以下命令将文件提取到同一目录:
$ unzip 24usingmanual.zip -
导航到
src目录:$ cd cd mztx-ext-2.4/src/ -
输入以下命令以编译源文件:
$ make -
打开引导配置文件:
$ sudo pico /boot/config.txt -
在
config.txt文件中,找到并取消以下代码行的注释:framebuffer_width=320 framebuffer_height=240 -
保存并退出文件。
-
现在,每次 Raspberry Pi 重启时,我们都需要执行一个命令来启动 TFT LCD 屏幕。为此,使用以下命令打开
rc.local文件:$ sudo pico /etc/rc.local -
将以下代码行添加到启动屏幕的文件中:
sudo /home/pi/mztx-ext-2.4/src/mztx06a & -
保存并退出文件。然后,使用以下命令重启 Raspberry Pi:
$ sudo reboot
现在,你可以移除你的 HDMI 显示器,开始使用你的 TFT LCD 屏幕。你必须记住的一件事是屏幕分辨率非常小,并且它没有针对编码进行优化。我们更喜欢使用 HDMI 显示器来执行下一节所需的重大代码修改。在这个项目中使用 TFT LCD 屏幕是为了满足恒温器的移动性和便携性要求。
优化 TFT LCD 屏幕的 GUI
我们在上一节配置的 TFT LCD 屏幕的分辨率仅为 320 x 240 像素,但我们创建的第一编程阶段中的窗口相当大。因此,在我们将 Python 代码复制并运行在树莓派上之前,我们需要在代码中调整一些参数。
在你的常规计算机上,从书籍源代码中获取这一章文件夹的地方,打开Thermostat_Stage2.py文件。此文件包含实现最佳尺寸所需修改的详细信息,并进行了一些细微的美观改动。你将使用这个文件,而不是我们在前一阶段使用的文件,在你的树莓派上。代码中的这些调整将在以下代码行中解释。
第一次主要改动是在端口名称上。对于树莓派,你需要将你在第一阶段使用的 Arduino 端口的名称更改为/dev/ttyACM0,这是在大多数情况下分配给 Arduino 的地址:
port = serial.Serial('/dev/ttyACM0',9600, timeout=1)
在这个程序文件中,Tkinter主窗口和matplotlib图的大小也调整以适应屏幕大小。如果你使用的是不同尺寸的屏幕,请相应地更改以下代码行:
top.minsize(320,160)
pyplot.figure(figsize=(4,3))
现在,随着前面的更改,GUI 窗口应该能够适应树莓派的屏幕。由于树莓派的屏幕将被用作恒温器应用的专用屏幕,我们需要调整屏幕上的文本大小以适当地适应窗口。在标签的声明中添加font=("Helvetica", 20)文本以增加字体大小。以下代码行显示了在标签上执行的变化,以包含传感器名称:
Tkinter.Label(top,
text="Humidity",
font=("Helvetica", 20)).grid(column=1, row=2)
同样,font选项也被添加到观察标签中:
HumdUnitLabel = Tkinter.Label(top,
text="%",
font=("Helvetica", 20))
观察单位的标签也进行了类似的修改:
HumdLabel.config(text=cleanText(reading[1]),
font=("Helvetica", 20))
Thermostat_ Stage2.py文件已包含前面的修改,并已准备好在您的 Raspberry Pi 上运行。在运行文件之前,我们首先需要将其复制到 Raspberry Pi 上。在这个阶段,USB 集线器将非常有用,可以复制文件。如果您没有 USB 集线器,您可以使用两个可用的 USB 端口同时连接 USB 闪存盘、鼠标和键盘。使用 USB 集线器,将包含 Python 文件的 USB 闪存盘连接到 USB 集线器的一端,并将它们复制到主目录。将 Arduino 板的 USB 端口连接到 USB 集线器的一端。从 Raspberry Pi 的开始菜单,通过导航到附件 | LXTerminal来打开LXTerminal程序。从主目录运行 Python 代码,你将能够在 Raspberry Pi 的屏幕上看到优化的用户界面窗口。如果本章中提到的每个步骤都执行正确,当你点击开始按钮时,你将能够看到传感器观察结果被打印出来:

在本章结束时,你可能想知道一个带有传感器、Arduino、Raspberry Pi 和 TFT 屏幕的移动单元可能是什么样子。以下图像显示了本章中给出的说明开发的样品恒温器。我们使用亚克力板将 Raspberry Pi 和 Arduino 板固定在一起,并创建了一个紧凑的形态:

故障排除
在这个项目阶段,你可能会遇到一些已知的问题。以下部分描述了这些问题及其快速修复方法:
-
Raspberry Pi 无法启动:
-
确保使用指定的工具正确格式化 SD 卡。如果 SD 卡没有正确准备,Raspberry Pi 将无法启动。
-
检查 HDMI 线和显示器,看它们是否工作正常。
-
确保电源适配器与 Raspberry Pi 兼容。
-
-
TFT 液晶屏未开启:
-
确保屏幕已正确连接到 Raspberry Pi 的 GPIO 引脚。
-
如果你使用的是其他 TFT 液晶屏,请确保从其数据表中确认你的屏幕不需要额外的电源。
-
使用优化 TFT 液晶屏的 GUI部分中描述的步骤检查屏幕是否正确配置。
-
-
Raspberry Pi 上传感器数据的刷新率较慢:
-
尝试减少 Arduino 发送的每个串行消息之间的延迟。
-
终止任何在后台运行的其他应用程序。
-
摘要
通过这个项目,我们成功创建了一个便携式和可部署的恒温器,使用 Arduino 进行温度、湿度和环境光监测。在这个过程中,我们使用必要的组件组装了恒温器传感器单元,并开发了定制的 Arduino 程序来支持它们。我们还利用了 Python 编程方法,包括使用Tkinter库进行 GUI 开发和使用matplotlib库进行绘图。在章节的后面部分,我们使用了 Raspberry Pi 将一个简单的项目原型转化为实际应用。从现在起,你应该能够开发出类似的项目,这些项目需要你观察和可视化实时传感器信息。
在接下来的工作中,我们将扩展这个项目以适应即将到来的主题,例如 Arduino 网络、云通信和远程监控。在恒温器项目的下一个阶段,我们将集成这些高级功能,使其成为一个真正有价值的 DIY 项目,可以在日常生活中使用。在下一章中,我们将开始我们的旅程的下一阶段,从制作简单的 Python-Arduino 项目过渡到互联网连接和远程访问的物联网项目。
第八章。Arduino 网络简介
到目前为止,我们使用硬连线串行连接与 Arduino 交互,使用串行监视器来观察 Arduino 的串行数据,以及使用 Python 串行库 (pySerial) 在 Arduino 和 Python 应用程序之间传输数据。在整个交换过程中,由于硬连线串行连接的限制,通信范围有限。作为解决方案,你可以使用无线协议,如 ZigBee、蓝牙 或其他射频通道来为远程串行接口建立通信通道。这些无线协议在远程硬件应用中被广泛使用,并且它们使用串行接口来传输数据。由于它们使用串行通信,这些协议在 Arduino 或 Python 方面几乎不需要额外的编程更改。然而,你可能需要额外的硬件来启用这些协议。这些协议的主要好处是它们非常容易实现。然而,它们的地理覆盖范围有限,数据带宽也有限。
除了串行通信方法之外,另一种远程访问你的 Arduino 设备的方法是使用计算机网络。如今,计算机网络是计算单元之间通信的最普遍方式。在接下来的两章中,我们将探讨使用 Arduino 和 Python 的各种网络技术,这些技术从建立非常基本的以太网连接到开发复杂的基于云的 Web 应用程序。
在本章中,我们将涵盖以下主题:
-
网络和硬件扩展的基本原理,这些扩展使得 Arduino 能够进行网络通信
-
用于在您的计算机上开发 超文本传输协议 (HTTP) 网络服务器的 Python 框架
-
将基于 Arduino 的 HTTP 客户端与 Python 网络服务器进行接口连接
-
物联网消息协议 MQTT(我们将在电脑上安装一个名为 Mosquitto 的中间件工具以启用 MQTT)
-
利用 MQTT 中使用的发布/订阅范式来开发 Arduino-Python 网络应用
Arduino 和计算机网络
计算机网络是一个庞大的领域,本书的主要目标不是涵盖网络的所有方面。然而,我们将尝试在需要应用这些知识的地方解释计算机网络的几个基本原理。与需要设备之间点对点连接的串行接口方法不同,基于网络的方法提供了对资源的分布式访问。特别是在需要单个硬件单元被多个端点访问的硬件应用中(例如,在个人电脑、移动电话或远程服务器中),计算机网络具有优越性。
在本节中,我们将介绍网络和硬件组件的基本原理,这些组件使得 Arduino 能够进行网络通信。在本章的后面部分,我们将使用 Arduino 库和内置示例来演示如何通过本地网络远程访问 Arduino。
网络基础知识
每当你看到一台计算机或移动设备时,你也在看着某种类型的计算机网络,它被用来将这些设备与其他设备连接起来。简单来说,计算机网络是一组相互连接的计算设备(也称为网络节点),允许这些设备之间交换数据。这些网络节点包括各种设备,如你的个人电脑、手机、服务器、平板电脑、路由器以及其他网络硬件。
根据地理位置、网络拓扑和组织范围等参数,计算机网络可以划分为多种类型。在地理规模方面,网络可以分为局域网(LAN)、家庭区域网(HAN)、广域网(WAN)等。当你使用你的家庭路由器连接到互联网时,你正在使用由你的路由器创建的 LAN。关于管理网络的机构,LAN 可以配置为内网、外网和互联网。互联网是任何计算机网络中最大的例子,因为它连接了全球部署的所有类型的网络。在你这本书中的各种项目实现中,你将主要使用你的 LAN 和互联网来在 Arduino、你的电脑、树莓派和云服务之间交换数据。
为了标准化网络节点之间的通信,各种管理机构和组织制定了一套称为协议的规则。在庞大的标准协议列表中,有一些协议是你在日常生活中经常使用的。与局域网相关的那些协议的例子包括以太网和 Wi-Fi。在 IEEE 802 系列标准中,IEEE 802.3 标准描述了局域网中节点之间不同类型的有线连接,也称为以太网。同样,无线局域网(也称为 Wi-Fi)是 IEEE 802.11 标准的一部分,其中通信通道使用无线频段来交换数据。
大多数使用 IEEE 802 标准(即以太网、Wi-Fi 等)部署的网络节点都有一个分配给网络接口硬件的唯一标识符,称为媒体访问控制(MAC)地址。这个地址由制造商分配,并且对于每个网络接口通常是固定的。在使用 Arduino 进行网络连接时,我们需要 MAC 地址来启用网络功能。MAC 地址是一个 48 位的地址,以人类友好的形式包含六组两位十六进制数字。例如,01:23:45:67:89:ab 是 48 位 MAC 地址的人类可读形式。
虽然 MAC 地址与硬件级别的(即,“物理”)协议相关联,但互联网协议(IP)是一种在互联网级别广泛使用的通信协议,它使得网络节点之间能够进行互连。在 IP 协议套件(IPv4)的版本 4 实现中,每个网络节点被分配一个 32 位的数字,称为IP 地址(例如,192.168.0.1)。当您将计算机、手机或任何其他设备连接到您的本地家庭网络时,您的路由器将为该设备分配一个 IP 地址。最流行的 IP 地址之一是 127.0.0.1,也称为本地主机IP 地址。除了网络分配给计算机的 IP 地址外,每台计算机还有一个与其关联的本地主机 IP 地址。当您想从同一设备内部访问或调用您的计算机时,本地主机 IP 地址非常有用。在远程访问应用程序的情况下,您需要知道网络分配的 IP 地址。
获取您计算机的 IP 地址
Arduino 是一种资源受限的设备,因此它只能展示有限的网络功能。当您与基于 Arduino 的项目合作,这些项目包括利用计算机网络时,您将需要一个服务器或网关接口。这些接口包括但不限于台式计算机、笔记本电脑、树莓派和其他远程计算实例。如果您将这些接口作为您硬件项目的一部分使用,您将需要它们的 IP 地址。确保它们与您的 Arduino 处于同一网络下。以下是在主要操作系统中获得 IP 地址的技术。
Windows
在 Windows 操作系统的多数版本中,您可以从控制面板中的网络连接实用程序中获取 IP 地址。导航到控制面板 | 网络和互联网 | 网络连接并打开本地连接状态窗口。点击详细信息按钮以查看网络连接详细信息窗口的详细信息。如您在此截图中所见,网络接口的 IP 地址在打开的窗口中列示为IPv4 地址:

您还可以使用内置的ipconfig实用程序获取您计算机的 IP 地址。打开命令提示符并输入以下命令:
> ipconfig
如您在以下截图中所见,您的计算机的 IP 地址列在以太网适配器下。如果您使用无线连接连接到您的网络,以太网适配器将被无线以太网适配器替换。

Mac OS X
如果您正在使用 Mac OS X,您可以从网络设置中获取 IP 地址。打开系统偏好设置并点击网络图标。您将看到一个类似于下一张截图的窗口。在左侧侧边栏中,点击您想要获取 IP 地址的接口。

如果你想通过终端获取 IP 地址,可以使用以下命令。此命令需要你输入接口的系统名称,en0:
$ ipconfig getifaddr en0
如果你连接到多个网络且不知道网络名称,你可以使用以下命令找到与电脑关联的 IP 地址列表:
$ ifconfig | grep inet
如此截图所示,你将得到与你的 Mac 电脑和其他网络参数相关的所有网络地址:

Linux
在 Ubuntu OS 上,你可以从网络设置实用程序中获取电脑的 IP 地址。要打开它,请转到系统设置 | 网络,然后点击电脑连接到家庭网络的适配器。你可以选择一个合适的适配器来获取 IP 地址,如图所示:

在基于 Linux 的系统上,有多种从命令行获取 IP 地址的方法。你可以在 Linux 环境中使用与在 Mac OS X 中相同的命令(ifconfig)来获取电脑的 IP 地址:
$ ifconfig
你可以从适当的适配器的inet addr字段中获取 IP 地址,如图所示:

如果你的操作系统支持,另一个可以用来获取 IP 地址的命令是hostname:
$ hostname –I
使用此实用程序获取 IP 地址时请小心,因为你可能不熟悉该实用程序的受支持命令选项,最终得到的是不同适配器的 IP 地址。
注意
如果你打算将你的 Arduino 连接到与电脑相同的局域网,请确保你选择了一个与电脑域名相同的正确 IP 地址。同时,请确保没有其他网络设备正在使用你为 Arduino 选择的相同 IP 地址。这种做法将帮助你避免网络中的 IP 地址冲突。
Arduino 网络扩展
在 Arduino 社区中,有多种硬件设备可用于为 Arduino 平台提供网络功能。在这些设备中,一些可以用作现有 Arduino 板的扩展,而其他则是具有网络功能的独立 Arduino 模块。最常用的扩展是 Arduino 以太网盾和 Arduino WiFi 盾。同样,Arduino Yún 是一个包含内置网络功能的独立 Arduino 平台的例子。在这本书中,我们将围绕 Arduino 以太网盾开发各种网络应用。还有一些其他扩展(Arduino GSM 盾)和独立 Arduino 平台(Arduino 以太网、Arduino Tre 等),但我们不会详细讨论它们。让我们熟悉以下 Arduino 扩展和板。
Arduino 以太网盾
Arduino 以太网盾是官方支持的开源网络扩展,旨在与 Arduino Uno 配合使用。以太网盾配备了 RJ45 连接器,以实现以太网联网。以太网盾设计用于安装在 Arduino Uno 的顶部,它将 Arduino Uno 的引脚布局扩展到板子的顶部。以太网盾还配备了 microSD 卡槽,用于在网络中存储重要文件。就像大多数这些盾扩展一样,以太网盾由其连接的 Arduino 板供电。

来源:arduino.cc/en/uploads/Main/ArduinoEthernetShield_R3_Front.jpg
每个以太网盾板都配备了一个唯一的硬件(MAC)地址。您可以在板子的背面看到它。您可能需要记下这个硬件地址,因为在接下来的练习中会经常需要它。同时,确保您熟悉安装 Arduino 以太网盾的步骤。在开始任何练习之前,从 SparkFun 或 Amazon 购买 Arduino 以太网盾模块。您可以在arduino.cc/en/Main/ArduinoEthernetShield找到有关此盾的更多信息。
Arduino WiFi 盾
Arduino WiFi 盾在安装在 Arduino 板上的布局方面与 Arduino 以太网盾相似。而不是以太网 RJ45 连接器,WiFi 盾包含用于实现无线联网的组件。使用 WiFi 盾,您可以连接到 IEEE 802.11(Wi-Fi)无线网络,这是目前将计算机连接到家庭网络最流行的方式之一。

来源:arduino.cc/en/uploads/Main/A000058_front.jpg
Arduino WiFi 盾需要通过 USB 连接器额外供电。它还包含一个 microSD 插槽用于保存文件。就像以太网盾一样,您可以在板子的背面查看 MAC 地址。更多关于 Arduino WiFi 盾的信息可以在arduino.cc/en/Main/ArduinoWi-FiShield找到。
Arduino Yún
与以太网盾和 WiFi 盾不同,Arduino Yún 是 Arduino 板的独立变体。它包括基于以太网和 Wi-Fi 的网络连接,以及基本的 Arduino 组件——微控制器。与 Uno 相比,Yún 配备了最新且更强大的处理单元。Yún 不仅支持传统的 Arduino 代码使用方式,还支持轻量级的 Linux 操作系统版本,提供类似于 Raspberry Pi 等单板计算机的功能。您可以在运行 Unix shell 脚本的同时使用 Arduino IDE 来编程 Yún。

来源:arduino.cc/en/uploads/Main/ArduinoYunFront_2.jpg
你可以在 Arduino 官方网站上找到更多关于 Yún 的信息,在arduino.cc/en/Main/ArduinoBoardYun。
Arduino 以太网库
Arduino 以太网库提供了对以太网协议的支持,因此也支持 Arduino 的以太网扩展,如以太网盾片。这是一个标准的 Arduino 库,它随 Arduino IDE 一起部署。
该库设计为在作为服务器部署时接受传入的连接请求,并在作为客户端使用时向其他服务器发起连接。由于 Arduino 板计算能力的限制,该库同时支持最多四个连接。要在 Arduino 程序中使用 Ethernet 库,你必须首先将其导入到 Arduino 草图:
#include <Ethernet.h>
Ethernet 库通过特定的类实现各种功能,以下将逐一描述。
小贴士
我们将仅描述这些类提供的重要方法。有关此库及其类的更多信息,请参阅arduino.cc/en/Reference/Ethernet。
Ethernet 类
Ethernet类是 Ethernet 库的核心类,它提供了初始化此库和网络设置的方法。对于任何想要通过以太网盾片使用 Ethernet 库建立连接的程序来说,这是一个必不可少的类。建立此连接所需的主要信息是设备的 MAC 地址。你需要创建一个变量,该变量将 MAC 地址作为 6 字节的数组,具体描述如下:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
Ethernet 库支持动态主机控制协议(DHCP),该协议负责为新网络节点动态分配 IP 地址。如果你的家庭网络配置为支持 DHCP,你可以使用Ethernet类的begin(mac)方法建立 Ethernet 连接:
Ethernet.begin(mac);
请记住,当你使用此类初始化 Ethernet 连接时,你只是在初始化 Ethernet 连接并设置 IP 地址。这意味着你仍然需要将 Arduino 配置为服务器或客户端,以便启用进一步的通信。
IPAddress 类
在需要手动为 Arduino 设备分配 IP 地址的应用中,你必须使用 Ethernet 库中的IPAddress类。此类提供了指定 IP 地址的方法,这可以是本地或远程的,具体取决于应用:
IPAddress ip(192,168,1,177);
使用此方法创建的 IP 地址可以用于我们在上一节中执行的初始化网络连接。如果您想为 Arduino 分配一个手动 IP 地址,可以使用带有 MAC 和 IP 地址的 begin(mac, ip) 方法:
Ethernet.begin(mac, ip);
服务器类
Server 类旨在在 Arduino 上使用 Ethernet 库创建服务器,该服务器监听指定端口的传入连接请求。当指定端口号的整数值时,EthernetServer() 方法将在 Arduino 上初始化服务器:
EthernetServer server = EthernetServer(80);
在上一行代码中指定端口 80(代表 TCP/IP 套件中的 HTTP 协议),我们已特别使用 Ethernet 库创建了一个网络服务器。要开始监听传入的连接请求,您必须在 server 对象上使用 begin() 方法:
server.begin();
一旦建立连接,您可以使用服务器类支持的各种方法来响应请求,例如 write()、print() 和 println()。
客户端类
Client 类提供了创建 Ethernet 客户端以连接和与服务器通信的方法。EthernetClient() 方法初始化一个客户端,该客户端可以使用其 IP 地址和端口号连接到特定的服务器。client 对象上的 connect(ip, port) 方法将与指定 IP 地址的服务器建立连接:
EthernetClient client;
client.connect(server, 80);
Client 类也包含 connected() 方法,该方法以二进制形式提供当前连接的状态。此状态可以是 true(已连接)或 false(未连接)。此方法对于定期监控连接状态很有用:
client.connected()
其他重要的客户端方法包括 read() 和 write()。这些方法帮助 Ethernet 客户端从服务器读取请求,并分别向服务器发送消息。
练习 1 – 一个网络服务器,您的第一个 Arduino 网络程序
测试 Arduino Ethernet 库和 Ethernet 扩展板最好的方式是使用与 Arduino IDE 一起部署的内置示例。如果您使用的是 Arduino IDE 1.x 版本,可以通过导航到 文件 | 示例 | Ethernet 来找到一系列的 Ethernet 示例。通过利用这些示例之一,我们将构建一个当通过网页浏览器请求时提供传感器值的网络服务器。由于 Arduino 将通过 Ethernet 连接到您的家庭网络,您将能够从网络中的任何其他计算机访问它。本练习的主要目标如下:
-
使用 Arduino Ethernet 库和 Arduino Ethernet 扩展板创建网络服务器
-
使用您的家庭电脑网络远程访问 Arduino
-
利用默认的 Arduino 示例,通过网络服务器提供湿度和运动传感器值
为了实现这些目标,练习被分为以下阶段:
-
使用您的 Arduino 和 Ethernet 扩展板设计并构建练习所需的硬件
-
以 Arduino IDE 中的默认示例作为练习的起点
-
修改示例以适应您的硬件设计并重新部署代码
以下是为此练习所需的电路的 Fritzing 图。您应该做的第一件事是将以太网盾安装到 Arduino Uno 的顶部。确保所有以太网盾的引脚都与 Arduino Uno 的相应引脚对齐。然后您需要连接之前使用的湿度传感器 HIH-4030 和 PIR 运动传感器。

注意
在部署 Arduino 硬件以实现无 USB 的远程连接时,您将不得不为板子提供外部电源,因为您不再有 USB 连接来为板子供电。
现在,使用 USB 线将您的 Arduino Uno 连接到计算机。您还需要使用以太网线将 Arduino 连接到您的本地家庭网络。为此,使用直通 CAT5 或 CAT6 线,并将线的一端连接到您的家庭路由器。这个路由器应该是提供您所使用计算机网络访问的同一设备。将以太网线的另一端连接到 Arduino 以太网盾板的以太网端口。如果物理级连接已经正确建立,您应该在该端口看到一个绿色的指示灯。

现在是时候开始编写您的第一个以太网示例了。在 Arduino IDE 中,通过导航到文件 | 示例 | 以太网 | WebServer来打开WebServer示例。如您所见,以太网库已包含在其他所需库和支持代码中。在代码中,您需要更改 MAC 和 IP 地址以使其适用于您的配置。虽然您可以从板子的背面获得以太网盾的 MAC 地址,但您必须根据您的家庭网络配置选择一个 IP 地址。既然您已经获得了您正在工作的计算机的 IP 地址,请选择该范围内的另一个地址。确保没有其他网络节点使用此 IP 地址。使用这些 MAC 和 IP 地址来更新代码中的以下值。当您处理 Arduino 以太网时,您需要为每个练习重复这些步骤:
byte mac[] = {0x90, 0xA2, 0xDA, 0x0D, 0x3F, 0x62};
IPAddress ip(10,0,0,75);
小贴士
在 IP 网络中,您的网络可见的 IP 地址范围是另一个称为子网或子网的地址的函数。您的局域网 IP 网络的子网可以帮助您在计算机 IP 地址范围内选择适合以太网盾的 IP 地址。您可以在en.wikipedia.org/wiki/Subnetwork上了解子网的基本知识。
在进一步深入代码之前,请使用这些修改编译代码并将它上传到 Arduino。一旦上传过程成功完成,打开一个网页浏览器并输入在 Arduino 草图中所指定的 IP 地址。如果一切顺利,您应该会看到显示模拟引脚值的文本。
为了更好地理解这里发生的情况,让我们回到代码。如您所见,在代码的开始部分,我们使用来自 Ethernet 库的EthernetServer方法在端口80初始化了 Ethernet 服务器库:
EthernetServer server(80);
在执行setup()函数期间,程序通过使用您之前定义的mac和ip变量,通过Ethernet.begin()方法通过 Ethernet 盾牌初始化了 Ethernet 连接。server.begin()方法将从这里启动服务器。如果您使用 Ethernet 库编写服务器代码,这两个步骤是启动服务器的必要步骤:
Ethernet.begin(mac, ip);
server.begin();
在loop()函数中,我们使用EthernetClient方法初始化一个client对象来监听传入的客户端请求。此对象将响应任何尝试通过端口80访问以太网服务器的连接客户端的请求:
EthernetClient client = server.available();
在收到请求后,程序将等待请求负载结束。然后,它将使用client.print()方法以格式化的 HTML 数据回复客户端:
while (client.connected()) {
if (client.available()) {
char c = client.read();
Serial.write(c);
# Response code
}
如果您尝试从浏览器访问 Arduino 服务器,您会看到网络服务器会回复客户端模拟引脚的读取值。现在,为了获得我们在硬件设计中连接的湿度和 PIR 传感器的正确值,您需要对代码进行以下修改。您会注意到,我们正在向客户端回复相对湿度的计算值,而不是所有模拟引脚的原始读取值。我们还修改了将在网页浏览器中打印的文本,以匹配正确的传感器标题:
if (c == '\n' && currentLineIsBlank) {
// send a standard http response header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println("Refresh: 5");
client.println();
client.println("<!DOCTYPE HTML>");
client.println("<html>");
float sensorReading = getHumidity(analogChannel, temperature);
client.print("Relative Humidity from HIH4030 is ");
client.print(sensorReading);
client.println(" % <br />");
client.println("</html>");
break;
}
在此过程中,我们还添加了一个 Arduino 函数getHumidity(),该函数将根据从模拟引脚观察到的值计算相对湿度。我们已经在之前的某个项目中使用了一个类似的功能来计算相对湿度:
float getHumidity(int analogChannel, float temperature){
float supplyVolt = 5.0;
int HIH4030_Value = analogRead(analogChannel);
float analogReading = HIH4030_Value/1023.0 * supplyVolt;
float sensorReading = 161.0 * analogReading / supplyVolt - 25.8;
float humidityReading = sensorReading / (1.0546 - 0.0026 * temperature);
return humidityReading;
}
您可以在测试阶段将这些更改应用到WebServerArduino 示例中,或者直接从您的代码目录中的Exercise 1 - Web Server文件夹打开WebServer_Custom.ino草图。正如您在打开的草图文件中可以看到的,我们已经修改了代码以反映这些更改,但您仍然需要将 MAC 和 IP 地址更改为适当的地址。完成这些小改动后,编译并将草图上传到 Arduino。
如果一切按计划进行,你应该能够使用你的网页浏览器访问网络服务器。在网页浏览器中打开你最近准备的 Arduino 的 IP 地址。你应该能够接收到以下截图显示的类似响应。尽管我们只通过这个草图显示湿度值,但你可以通过额外的client.print()方法轻松地附加运动传感器的值。

就像我们在这次练习中实现的机制一样,网络服务器响应网页浏览器的请求,并交付你寻找的网页。尽管这种方法非常流行并且被普遍用于交付网页,但与传感器信息的实际大小相比,有效载荷包含了很多额外的元数据。此外,使用以太网服务器库实现的网络服务器占用了大量的 Arduino 资源。由于 Arduino 是一个资源受限的设备,因此不适合运行服务器应用程序,因为 Arduino 的资源应该优先用于处理传感器而不是通信。此外,使用以太网库创建的网络服务器一次只能支持非常有限数量的连接,这使得它对于大规模应用程序和多用户系统来说不可用。
解决这个问题的最佳方法是通过使用 Arduino 作为客户端设备,或者使用专为与资源受限的硬件设备一起工作而设计的轻量级通信协议。在接下来的几节中,你将学习并实现这些方法,用于在以太网上进行 Arduino 通信。
使用 Python 开发 Web 应用程序
通过实现前面的程序,你已经在 Arduino 上启用了网络功能。在先前的例子中,我们使用以太网库中的方法创建了一个 HTTP 网络服务器。通过创建 Arduino 网络服务器,我们使 Arduino 资源在网络上可用。同样,Python 也通过各种库提供扩展性,以创建网络服务器接口。通过在你的计算机或其他设备(如树莓派)上运行基于 Python 的网络服务器,你可以避免使用 Arduino 来托管网络服务器。使用 Python 等高级语言创建的 Web 应用程序也可以提供与 Arduino 相比的额外功能和扩展性。
在本节中,我们将使用 Python 库web.py来创建一个 Python 网络服务器。我们还将使用这个库来创建交互式网络应用程序,这将允许在 Arduino 客户端和网页浏览器之间传输数据。在你学习了web.py的基础之后,我们将通过串行端口将 Arduino 与web.py接口连接,使 Arduino 可以通过 Python 网络服务器访问。然后,我们将升级 Arduino 的通信方法,从串行接口升级到基于 HTTP 的消息。
Python 网络框架 – web.py
可以使用 Python 和各种网络框架(如 Django、bottle、Pylon 和 web.py)开发网络服务器。我们选择 web.py 作为首选的网络框架,因为它简单而功能强大。
web.py 库最初是由已故的 Aaron Swartz 开发的,目的是开发一种简单直接的方法,使用 Python 创建网络应用程序。这个库提供了两个主要方法,GET 和 POST,以支持 HTTP 表示状态转移(REST)架构。这个架构旨在通过在客户端和服务器之间发送和接收数据来支持 HTTP 协议。今天,REST 架构被大量网站用于通过 HTTP 传输数据。
安装 web.py
要开始使用 web.py,您需要使用 Setuptools 安装 web.py 库。我们在第一章中安装了 Setuptools,用于各种操作系统,Python 和 Arduino 入门。在 Linux 和 Mac OS X 上,您可以在终端中执行以下任一命令来安装 web.py:
$ sudo easy_install web.py
$ sudo pip install web.py
在 Windows 上,打开 命令提示符 并执行以下命令:
> easy_install.exe web.py
如果 Setuptools 设置正确,您应该能够轻松地安装库。要验证库的安装,打开 Python 交互式提示符并运行此命令,以查看是否可以无错误地导入库:
>>> import web
您的第一个 Python 网络应用程序
使用 web.py 实现网络服务器是一个非常简单直接的过程。web.py 库需要声明一个强制方法 GET,才能成功启动网络服务器。当客户端尝试使用网页浏览器或其他客户端访问服务器时,web.py 会接收到一个 GET 请求,并返回由方法指定数据。要使用 web.py 库创建一个简单的网络应用程序,请使用以下代码行创建一个 Python 文件,并使用 Python 执行该文件。您也可以从本章的代码文件夹中运行 webPyBasicExample.py 文件:
import web
urls = (
'/', 'index'
)
class index:
def GET(self):
return "Hello, world!"
if __name__ == "__main__":
app = web.application(urls, globals())
app.run()
执行后,您将看到服务器现在正在运行,可以通过 http://0.0.0.0:8080 地址访问。由于服务器程序在 0.0.0.0 IP 地址上运行,您可以使用同一台计算机、localhost 或同一网络上的任何其他计算机访问它。
要检查服务器,请打开网页浏览器并转到 http://0.0.0.0:8080。当您尝试从同一台计算机访问服务器时,您也可以使用 http://127.0.0.1:8080 或 http://localhost:8080。127.0.0.1 IP 地址实际上代表 localhost,即程序运行的同一台计算机的网络地址。您将在浏览器中看到服务器响应,如下面的截图所示:

要了解这段简单代码是如何工作的,请查看前一个代码片段中的 GET 方法。正如你所见,当网络浏览器请求 URL 时,GET 方法将 Hello, world! 字符串返回给浏览器。同时,你还可以观察到代码中的另外两个强制性的 web.py 组件:urls 和 web.application() 方法。web.py 库要求在 urls 变量的声明中初始化响应位置。每个基于 web.py 的 Web 应用程序都需要调用 application(urls, global()) 方法来初始化 Web 服务器。默认情况下,web.py 应用程序在端口号 8080 上运行,可以通过在执行时指定另一个端口号来更改。例如,如果你想将你的 web.py 应用程序运行在端口号 8888 上,请执行以下命令:
$ python webPyBasicExample.py 8888
尽管这仅返回简单的文本,但你现在已经成功使用 Python 创建了你的第一个网络应用程序。我们将从这里继续前进,在接下来的章节中使用 web.py 库创建更复杂的应用程序。为了开发这些复杂的应用程序,我们需要的不仅仅是 GET 方法。让我们开始探索高级概念,以进一步加深你对 web.py 库的熟悉程度。
开发复杂 Web 应用程序所必需的 web.py 概念
web.py 库被设计用来提供方便且简单的方法,使用 Python 开发动态网站和 Web 应用程序。使用 web.py,通过仅利用一些额外的 Python 概念以及你已知的知识,可以非常容易地构建复杂网站。由于这个有限的学习曲线和易于实现的方法,web.py 是创建 Web 应用程序的最快方式之一。让我们从详细了解这些 web.py 概念开始。
处理 URL
你可能已经注意到,在我们的第一个 web.py 程序中,我们定义了一个名为 urls 的变量,它指向 Index 类的根位置(/):
urls = (
'/', 'index'
)
在前面的声明中,第一部分 '/' 是一个正则表达式,用于匹配实际的 URL 请求。你可以使用正则表达式来处理 web.py 服务器接收到的复杂查询,并将它们指向适当的类。在 web.py 中,你可以将不同的着陆页位置与适当的类关联起来。例如,如果你想将 /data 位置重定向到 data 类(除了 Index 类),你可以按如下方式更改 urls 变量:
urls = (
'/', 'index',
'/data', 'data',
)
根据这项规定,当客户端发送请求以访问 http://<ip-address>:8080/data 地址时,请求将被导向 data 类,然后是该类的 GET 或 POST 方法。
GET 和 POST 方法
在练习 1 中,我们创建了一个基于 Arduino 的运行在端口80上的网络服务器,我们使用网页浏览器来访问这个网络服务器。网页浏览器是用于访问网络服务器最受欢迎的客户端类型之一;cURL、Wget 和网页爬虫是其他类型。网页浏览器使用 HTTP 与任何网络服务器进行通信,包括我们使用的 Arduino 网络服务器。GET和POST是 HTTP 协议支持的两种基本方法,用于处理来自网页浏览器的服务器请求。
无论你试图在浏览器或其他 HTTP 客户端中打开一个网站,实际上你都是在请求从网络服务器获取GET函数;例如,当你打开一个网站 URL,http://www.example.com/,你是在请求托管此网站的网络服务器为你提供'/'位置的GET请求。在处理 URL部分,你学习了如何将web.py类与 URL 着陆位置关联起来。使用web.py库提供的GET方法,你可以将GET请求与单个类关联起来。一旦捕获到GET请求,你需要返回适当的值作为对客户端的响应。以下代码片段显示了当任何人向'/'位置发起GET请求时,GET()函数将被如何调用:
def GET(self):
f = self.submit_form()
f.validates()
t = 75
return render.test(f,t);
HTTP 协议的POST函数主要用于向网络服务器提交表单或其他数据。在大多数情况下,POST嵌入在网页中,当用户提交携带POST函数的组件时,会生成对服务器的请求。web.py库也提供了POST()函数,当 Web 客户端尝试使用POST方法联系web.py服务器时被调用。在大多数POST()函数的实现中,请求包括通过表单提交的一些类型的数据。你可以使用f['Celsius'].value检索单个表单元素,这将给出与名为Celsius的表单元素相关联的值。一旦POST()函数执行了提供的操作,你可以在对POST请求的响应中返回适当的信息:
def POST(self):
f = self.submit_form()
f.validates()
c = f['Celsius'].value
t = c*(9.0/5.0) + 32
return render.test(f,t)
模板
现在你已经知道了如何将 HTTP 请求重定向到适当的 URL,以及如何实现响应这些 HTTP 请求的方法(即GET和POST)。但是,一旦收到请求,需要渲染的网页怎么办?为了理解渲染过程,让我们从在web.py程序将要放置的同一目录下创建一个名为templates的文件夹开始。这个文件夹将存储在请求网页时使用的模板。你必须使用template.render()函数在程序中指定这个模板文件夹的位置,如下代码行所示:
render = web.template.render('templates')
一旦实例化了渲染文件夹,就是时候为你的程序创建模板文件了。根据你程序的要求,你可以创建尽可能多的模板文件。在 web.py 中,使用名为 Templetor 的语言创建这些模板文件。你可以在 webpy.org/templetor 上了解更多信息。使用 Templetor 创建的每个模板文件都需要以 .html 扩展名存储为 HTML 格式。
让我们在 templates 文件夹中使用文本编辑器创建一个名为 test.html 的文件,并将以下代码片段粘贴到文件中:
$def with(form, i)
<form method="POST">
$:form.render()
</form>
<p>Value is: $:i </p>
如前述代码片段所示,模板文件以 $def with() 表达式开始,其中你需要在括号内指定作为变量的输入参数。一旦模板被渲染,这些将是你可以用于网页的唯一变量;例如,在前面的代码片段中,我们传递了两个变量(form 和 i)作为输入变量。我们使用 $:form.render() 利用 form 对象在网页内进行渲染。当你需要渲染 form 对象时,你可以直接通过声明它(即,$:i)来传递其他变量。Templetor 将按原样渲染模板文件的 HTML 代码,并在使用变量的实例中使用它们。
现在你已经有一个模板文件,test.html,准备在你的 web.py 程序中使用。每当执行 GET() 或 POST() 函数时,你必须向请求客户端返回一个值。虽然你可以为这些请求返回任何变量,包括 None,但你必须渲染一个模板文件,其中响应与加载网页相关联。你可以使用 render() 函数返回模板文件,后跟模板文件的文件名和输入参数:
return render.test(f, i);
如前一行代码所示,我们通过指定 render.test() 函数返回渲染的 test.html 页面,其中 test() 只是文件名,不带 .html 扩展名。该函数还包括一个表单对象 f 和变量 i,它们将被作为输入参数传递。
表单
web.py 库提供了使用 Form 模块创建表单元素的简单方法。此模块包括创建 HTML 表单元素、从用户处获取输入并在将其用于 Python 程序之前验证这些输入的功能。在下面的代码片段中,我们使用 Form 库创建了两个表单元素,Textbox 和 Button:
submit_form = form.Form(
form.Textbox('Celsius', description = 'Celsius'),
form.Button('submit', type="submit", description='submit')
)
除了Textbox(从用户那里获取文本输入)和Button(提交表单)之外,Form模块还提供了一些其他表单元素,例如Password用于获取隐藏的文本输入,Dropbox用于从下拉列表中获取互斥输入,Radio用于从多个选项中获取互斥输入,以及Checkbox用于从给定选项中选择二进制输入。虽然所有这些元素都非常容易实现,但你应该只根据程序需求选择表单元素。
在web.py的Form实现中,每次表单提交时网页都需要执行POST方法。正如你可以在以下模板文件中表单的实现中看到,我们明确声明表单提交方法为POST:
$def with(form, i)
<form method="POST">
$:form.render()
</form>
Exercise 2 – 使用 Arduino 串行接口玩转 web.py 概念
现在你已经对构建 Web 应用程序所使用的web.py基本概念有了大致的了解。在这个练习中,我们将利用你学到的概念来创建一个应用程序,为 Arduino 提供传感器信息。由于这个练习的目的是展示用于 Arduino 数据的web.py服务器,我们不会使用以太网盾进行通信。相反,我们将使用串行接口捕获 Arduino 数据,同时使用web.py服务器响应来自不同客户端的请求。
正如你在以下图中可以看到,我们正在使用与练习 1 中设计的相同硬件,但没有使用与家庭路由器的以太网连接。运行web.py服务器的你的计算机也是你家庭网络的一部分,它将服务客户端请求。

在第一步,我们将编写 Arduino 代码,定期将湿度传感器的值发送到串行接口。对于 Arduino 代码,从你的代码目录的Exercise 2文件夹中打开WebPySerialExample_Arduino.ino草图。正如你在以下 Arduino 草图代码片段中可以看到,我们正在将模拟端口上的原始值发送到串行接口。现在编译并上传草图到你的 Arduino 板。从 Arduino IDE 打开串行监视器窗口以确认你正在接收原始湿度观测值。一旦确认,关闭串行监视器窗口。如果串行监视器窗口正在使用端口,你将无法运行 Python 代码:
void loop() {
int analogChannel = 0;
int HIH4030_Value = analogRead(analogChannel);
Serial.println(HIH4030_Value);
delay(200);
}
一旦 Arduino 代码运行正常,就是时候执行包含web.py服务器的 Python 程序了。这个练习的 Python 程序位于WebPySerialExample_Python目录中。在你的代码编辑器中打开webPySerialExample.py文件。这个 Python 程序分为两个部分:使用pySerial库从串行接口捕获传感器数据,以及使用基于web.py服务器的服务器来响应用户请求:
在代码的第一个阶段,我们使用pySerial库的Serial()方法来接口串行端口。不要忘记根据你的计算机、操作系统和使用的物理端口更改串行端口名称:
import serial
port = serial.Serial('/dev/tty.usbmodemfa1331', 9600, timeout=1)
一旦创建了串行端口的port对象,程序就开始使用readline()方法读取来自物理端口的文本。使用relativeHumidity()函数,我们将原始湿度数据转换为适当的相对湿度观测值:
line = port.readline()
if line:
data = float(line)
humidity = relativeHumidity(line, 25)
在网络服务器端,我们将使用上一节中学到的所有主要的web.py组件来完成这个目标。作为其中的一部分,我们正在实现一个用于温度值的输入表单。我们将捕获这个用户输入,并利用原始传感器数据来计算相对湿度。因此,我们需要定义render对象以使用template目录。在这个练习中,我们只使用默认的着陆页位置('/')作为网络服务器,它指向Index类:
render = web.template.render('templates')
正如你在WebPySerialExample_Python文件夹中可以看到的,我们有一个名为templates的目录。这个目录包含一个名为base.html的模板。由于这是一个 HTML 文件,如果你只是点击文件,它很可能会在网页浏览器中打开。确保你在文本编辑器中打开文件。在打开的文件中,你会看到我们使用$def with(form, humidity)初始化模板文件。在这个初始化中,form和humidity是模板在渲染过程中所需的输入变量。模板使用$:form.render()方法声明实际的<form>元素,同时使用$humidity变量显示湿度值:
<form method="POST">
$:form.render()
</form>
<h3>Relative Humidity is:</h3>
<p name="temp">$humidity </p>
虽然模板文件渲染了form变量,但我们必须在 Python 程序中首先定义这个变量。正如你在下面的代码片段中可以看到的,我们使用web.py库的form.Form()方法声明了一个名为submit_form的变量。submit_form变量包括一个用于捕获温度值的Textbox元素和一个用于启用提交操作的Button元素:
submit_form = form.Form(
form.Textbox('Temperature', description = 'Temperature'),
form.Button('submit', type="submit", description='submit')
)
当你想访问submit_form变量的当前提交值时,你必须使用validates()方法验证表单:
f = self.submit_form()
f.validates()
现在我们已经设计了面向用户的网页和输入组件,用于练习。是时候定义两个主要的方法,GET 和 POST,以响应来自网页的请求。当你启动或刷新网页时,web.py 服务器生成 GET 请求,然后由 Index 类的 GET 函数处理。所以在 GET 方法的执行过程中,程序从串口获取最新的原始湿度值,并使用 relativeHumidity() 方法计算相对湿度。
注意
在处理 GET 请求的过程中,我们没有提交包含用户输入的表单。因此,在 GET 方法中,我们将使用 relativeHumidity() 方法的默认温度值(25)。
一旦得到湿度值,程序将使用 render.base() 函数渲染 base 模板,如下面的代码片段所示,其中 base() 指的是基本模板:
def GET(self):
f = self.submit_form()
f.validates()
line = port.readline()
if line:
data = float(line)
humidity = relativeHumidity(line, 25)
return render.base(f,humidity);
else:
return render.base(f, "Not valid data");
与 GET 方法相反,当表单提交到网页时,将调用 POST 方法。提交的表单包括用户提供的温度值,该值将用于获取相对湿度值。像 GET() 函数一样,POST() 函数在计算湿度后也会渲染带有最新湿度值的 base 模板:
def POST(self):
f = self.submit_form()
f.validates()
temperature = f['Temperature'].value
line = port.readline()
if line:
data = float(line)
humidity = relativeHumidity(line, float(temperature))
return render.base(f, humidity);
else:
return render.base(f, "Not valid data");
现在是时候运行基于 web.py 的网络服务器了。在 Python 程序中,进行必要的更改以适应串口名称和其他适当的值。如果一切配置正确,你将能够在终端中无错误地执行程序。你可以从同一台计算机上的网络浏览器访问运行在端口 8080 上的网络服务器,即 http://localhost:8080。现在练习的目标是演示从你的家庭网络远程访问网络服务器,你可以通过在网络上另一台计算机上打开网站来实现,即 http://<ip-address>:8080,其中 <ip-address> 指的是运行 web.py 服务的计算机的 IP 地址。

前面的截图显示了在网页浏览器中打开网络应用程序时的外观。当你加载网站时,你将能够看到使用 GET 方法获得的相对湿度值。现在你可以输入适当的温度值并按下 提交 按钮来调用 POST 方法。在成功执行后,你将能够看到基于你提交的温度值计算出的最新相对湿度值。
基于 Arduino 和 Python 的 RESTful 网络应用程序
在上一个练习中,我们使用了web.py库实现了GET和POST请求。这些请求实际上是万维网(WWW)中最流行的通信架构之一,称为 REST。REST 架构通过 HTTP 协议实现客户端-服务器范式,用于POST、READ和DELETE等操作。使用web.py实现的GET()和POST()函数是这些标准 HTTP REST 操作的功能子集,即GET、POST、UPDATE和DELETE。REST 架构是为网络应用程序、网站和 Web 服务设计的,以便通过基于 HTTP 的调用建立通信。REST 架构不仅是一套标准规则,它还利用了现有的 Web 技术和协议,使其成为我们今天使用的多数网站的核心组件。正因为如此,万维网可以被认为是基于 REST 架构的最大实现。
设计基于 REST 的 Arduino 应用程序
REST 架构使用客户端-服务器模型,其中服务器在网络中充当中央节点。它响应查询它的分布式网络节点(称为客户端)提出的请求。在这个范例中,客户端向服务器发起一个指向服务器状态的请求,而服务器响应状态请求而不存储客户端上下文。这种通信始终是单向的,并且始终由客户端发起。

要进一步解释GET和POST请求的状态传输,请查看之前的图示。当客户端使用 URL 向服务器发送GET请求时,服务器以 HTTP 响应的形式返回原始数据。同样,在POST请求中,客户端将数据作为有效载荷发送到服务器,而服务器仅以“已接收确认”消息响应。
REST 方法相对简单,可以使用简单的 HTTP 调用来实现和开发。我们将开始开发基于 REST 请求的 Arduino 网络应用程序,因为它们易于实现和理解,并且可以通过示例直接获得。我们将首先单独实现基于 REST 的 Arduino 客户端,用于 HTTP 的GET和POST方法。在本章的后面,我们将通过同一个 Arduino REST 客户端结合GET和POST方法,同时使用web.py开发 HTTP 服务器。
使用 Arduino 的 GET 请求进行工作
在这个练习中,我们将使用web.py开发的 HTTP 服务器,在 Arduino 上实现 HTTP GET客户端。这个编程练习的前提是使用以太网盾扩展和以太网库来开发一个支持GET请求的物理 Arduino HTTP 客户端。
生成 GET 请求的 Arduino 代码
Arduino IDE 附带了一些使用以太网库的基本示例。其中之一是WebClient,可以通过导航到文件 | 示例 | 以太网 | WebClient找到。它旨在通过在 Arduino 上实现 HTTP 客户端来演示GET请求。在 Arduino IDE 中打开这个草图,因为我们将要使用这个草图并对其进行修改以适应我们创建的 Arduino 硬件。
在打开的草图(sketch)中,你需要首先更改 Arduino 以太网盾(Arduino Ethernet Shield)的 IP 地址和 MAC 地址。将以下变量替换为适合你系统的变量。以下代码片段显示了我们的硬件的 IP 地址和 MAC 地址,你需要将其更改为适应你的设备:
byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0x47, 0x28 };
IPAddress ip(10,0,0,75);
如你所见,示例使用 Google 作为服务器以获取响应。你需要将这个地址更改为反映你的电脑的 IP 地址,该电脑将托管web.py服务器:
char server[] = "10.0.0.20";
在setup()函数中,你将不得不再次更改服务器 IP 地址。还将默认的 HTTP 端口(80)更改为web.py使用的端口(8080):
if (client.connect(server, 8080)) {
Serial.println("connected");
// Make a HTTP request:
client.println("GET /data HTTP/1.1");
client.println("Host: 10.0.0.20");
client.println("Connection: close");
client.println();
}
一旦你完成了所有这些更改,请转到Arduino_GET_Webpy\ArduinoGET文件夹,并打开ArduinoGET.ino草图。将你的修改后的草图与此草图进行比较,并进行适当的更改。现在你可以保存你的草图并编译你的代码以查找错误。
在这个阶段,我们假设你已经将 Arduino 以太网盾安装在了 Arduino Uno 上。使用以太网线将以太网盾连接到你的本地网络,并使用 USB 线将 Uno 连接到你的电脑。将草图上传到 Arduino 板,并打开串行监视器窗口以检查活动。在这个阶段,Arduino 无法连接到服务器,因为你的web.py服务器尚未运行。你现在可以关闭串行监视器。
使用 web.py 处理 GET 请求的 HTTP 服务器
在你的第一个 web.py 应用程序中,你开发了一个当从网页浏览器请求时返回 Hello, world! 的服务器。尽管它能够执行所有这些额外任务,但你的网页浏览器在其核心上仍然是一个 HTTP 客户端。这意味着如果你的第一个 web.py 服务器代码能够响应网页浏览器发出的 GET 请求,它也应该能够响应 Arduino 网络客户端。为了验证这一点,打开你的第一个 web.py 程序,webPyBasicExample.py,并将返回的字符串从 Hello World! 更改为 test。我们进行这个字符串更改是为了区分这个程序的其他实例。从终端执行 Python 程序,并在 Arduino IDE 中再次打开 串行监视器 窗口。这次,你将能够看到你的 Arduino 客户端正在接收它发送到 web.py 服务器的 GET 请求的响应。正如你在下面的屏幕截图中所看到的,你将能够在 串行监视器 窗口中看到打印的 test 字符串,这是 web.py 服务器为 GET 请求返回的:

尽管在这个例子中我们为 GET 请求返回了一个简单的字符串,但你可以将这种方法扩展以从网络服务器获取不同的用户指定参数。这种 GET 实现可以用于大量需要 Arduino 从用户或其他程序重复输入的应用程序。但如果网络服务器需要从 Arduino 获取输入呢?在这种情况下,我们将不得不使用 POST 请求。让我们开发一个 Arduino 程序来适应 HTTP POST 请求。
与 Arduino 的 POST 请求一起工作
由于我们现在已经实现了 GET 请求,我们可以使用类似的方法来练习 POST 请求。在实现 POST 请求时,我们不是要求服务器为状态请求提供响应,而是将从 Arduino 发送的传感器数据作为有效载荷。同样,在服务器端,我们将利用 web.py 接收 POST 请求并通过网页浏览器显示它。
用于生成 POST 请求的 Arduino 代码
从代码仓库的 Arduino_POST_Webpy\ArduinoPOST 文件夹中打开 Arduino 脚本 ArduinoPOST.ino。与之前的练习一样,你首先必须提供你的 Arduino 的 IP 地址和 MAC 地址。
完成这些基本更改后,观察以下代码片段以了解 POST 请求的实现。你可能注意到,我们正在创建 POST 请求的有效载荷,作为变量 data 从模拟引脚 0 获得的值:
String data;
data+="";
data+="Humidity ";
data+=analogRead(analogChannel);
在下面的 Arduino 代码中,我们首先使用 Ethernet 库创建一个client对象。在重复的loop()函数中,我们将使用这个client对象连接到运行在我们电脑上的web.py服务器。你必须将connect()方法中的 IP 地址替换为你的web.py服务器的 IP 地址。一旦连接,我们将创建一个包含我们之前计算出的有效载荷数据的自定义POST消息。Arduino 的loop()函数将定期将此代码示例生成的更新后的传感器值发送到web.py服务器:
if (client.connect("10.0.0.20",8080)) {
Serial.println("connected");
client.println("POST /data HTTP/1.1");
client.println("Host: 10.0.0.20");
client.println("Content-Type: application/x-www-form-urlencoded");
client.println("Connection: close");
client.print("Content-Length: ");
client.println(data.length());
client.println();
client.print(data);
client.println();
Serial.println("Data sent.");
}
完成更改后,编译并将此草图上传到 Arduino 板。由于web.py服务器尚未实现,来自 Arduino 的POST请求将无法成功到达其目的地,因此让我们创建一个web.py服务器来接受POST请求。
使用 web.py 处理 POST 请求的 HTTP 服务器
在这个POST方法的实现中,我们需要两个web.py类,index和data,分别用于独立处理来自网页浏览器和 Arduino 的请求。由于我们将使用两个独立的类来更新公共传感器值(即humidity和temperature),我们将它们声明为全局变量:
global temperature, humidity
temperature = 25
正如你可能在 Arduino 代码中注意到的(client.println("POST /data HTTP/1.1")),我们正在将POST请求发送到位于/data的 URL。同样,我们将使用默认的根位置,'/',来处理来自网页浏览器的任何请求。这些根位置的请求将由index类处理,正如我们在练习 2 中所讨论的那样:
urls = (
'/', 'index',
'/data','data',
)
data类负责处理来自/data位置的任何POST请求。在这种情况下,这些POST请求包含由 Arduino POST客户端附加的传感器信息的有效载荷。在接收到消息后,该方法将有效载荷字符串拆分为传感器类型和值,在此过程中更新全局humidity变量的值:
class data:
def POST(self):
global humidity
i = web.input()
data = web.data()
data = data.split()[1]
humidity = relativeHumidity(data,temperature)
return humidity
从 Arduino 接收到的每个POST请求都会更新原始湿度值,该值由data变量表示。我们正在使用练习 2 中的相同代码从用户那里获取手动温度值。相对湿度值humidity是根据你使用网页浏览器更新的温度值和原始湿度值来更新的。

要查看 Python 代码,从代码仓库中打开 WebPyEthernetPOST.py 文件。在做出适当的更改后,从终端执行代码。如果你在终端中没有收到来自 Arduino 的任何更新,你应该重新启动 Arduino 以重新建立与 web.py 服务器的连接。一旦你在终端中开始看到 Arduino POST 请求的周期性更新,请在浏览器中打开网页应用程序的位置。你将能够看到类似于前面的截图。在这里,你可以使用表单提交手动温度值,而浏览器将根据输入的温度值重新加载并更新相对湿度。
练习 3 – 一个 RESTful Arduino 网络应用程序
本练习的目标是简单地将你在前两个部分中学到的 GET 和 POST 方法结合起来,以便使用 Arduino 和 Python 创建一个完整的 REST 体验。这个练习的架构可以描述如下:
-
Arduino 客户端定期使用
GET请求从服务器获取传感器类型。它使用这个传感器类型来选择一个用于观察的传感器。在我们的例子中,它是一个湿度传感器或运动传感器。 -
网络服务器通过返回用户通过网页应用程序选择的传感器当前类型来响应
GET请求。用户通过网页应用程序提供此选择。 -
接收到传感器类型后,Arduino 客户端利用
POST将传感器观察发送到服务器。 -
网络服务器接收
POST数据并更新特定传感器类型的传感器观察。 -
在用户端,网络服务器通过网页浏览器获取当前传感器类型。
-
当浏览器中的 提交 按钮被按下时,服务器使用最新的值更新浏览器中的传感器值。
练习的 Arduino 脚本
使用我们构建的相同 Arduino 硬件,从 Exercise 3 - RESTful application Arduino and webpy 代码文件夹中打开名为 WebPyEthernetArduinoGETPOST.ino 的 Arduino 脚本。正如我们在练习架构中之前所描述的,Arduino 客户端应定期向服务器发送 GET 请求,并从响应中获取传感器类型的对应值。在比较传感器类型后,Arduino 客户端从 Arduino 引脚获取当前的传感器观察,并使用 POST 将该观察发送回服务器:
if (client.connected()) {
if (client.find("Humidity")){
# Fetch humidity sensor value
if (client.connect("10.0.0.20",8080)) {
# Post humidity values
}
}
else{
# Fetch motion sensor value
if (client.connect("10.0.0.20",8080)) {
# Post motion values
}
}
# Add delay
}
在代码中更改适当的服务器 IP 地址后,编译并将其上传到 Arduino。打开 串行监视器 窗口,在那里你会找到不成功的连接尝试,因为你的 web.py 服务器尚未运行。关闭你电脑上运行的任何其他 web.py 服务器实例或程序。
支持 REST 请求的 web.py 应用程序
从Exercise 3 - RESTful application Arduino and webpy代码文件夹中打开WebPyEthernetGETPOST.py文件。如您所见,基于web.py的 Web 服务器实现了两个独立的类,index和data,分别支持 Web 浏览器和 Arduino 客户端的 REST 架构。我们引入了一个新的概念,用于Form元素,称为Dropdown()。使用此Form方法,您可以实现下拉选择菜单,并要求用户从选项列表中选择一个选项:
form.Dropdown('dropdown',
[('Humidity','Humidity'),('Motion','Motion')]),
form.Button('submit',
type="submit", description='submit'))
在之前的web.py程序中,我们为index类实现了GET和POST方法,只为data类实现了POST方法。在接下来的练习中,我们还将为data类添加GET方法。当对/data位置发起GET请求时,此方法返回sensorType变量的值。从用户的角度来看,当表单带有选项提交时,sensorType变量的值会更新。此操作会将选定的值发送到index类的POST方法,最终更新sensorType值:
class data:
def GET(self):
return sensorType
def POST(self):
global humidity, motion
i = web.input()
data = web.data()
data = data.split()[1]
if sensorType == "Humidity":
humidity = relativeHumidity(data,temperature)
return humidity
else:
motion = data
return motion
在运行此 Python 程序之前,请确保您已经检查了代码的每个组件,并在需要的地方更新了值。然后从终端执行代码。您的网络服务器现在将在本地计算机上的端口号8080上运行。如果 Arduino 设备的连接尝试失败,请重新启动 Arduino 设备。为了测试您的系统,请从您的网络浏览器打开网络应用程序。您将在浏览器中看到一个网页打开,如下面的截图所示:

在按下提交按钮之前,您可以从下拉菜单(湿度或运动)中选择传感器类型。提交后,您将能够看到页面已更新为适当的传感器类型及其当前值。
我们为什么需要一个资源受限的消息协议?
在上一节中,你学习了如何使用 HTTP REST架构在 Arduino 和主机服务器之间发送和接收数据。HTTP 协议最初是为了通过互联网上的网页提供文本数据而设计的。HTTP 使用的数据传输机制需要相对大量的计算和网络资源,这对于计算机系统可能是足够的,但对于资源受限的硬件平台(如 Arduino)来说可能不够。正如我们之前讨论的,HTTP REST 架构实现的客户端-服务器范例创建了一个紧密耦合的系统。在这个范例中,双方(客户端和服务器)都需要持续活跃,或者说是“活着”,以响应。此外,REST 架构只允许从客户端到服务器的单向通信,其中请求总是由客户端初始化,服务器响应客户端。这种基于请求-响应的架构由于以下原因(但不限于)不适合受限的硬件设备:
-
这些设备应避免主动通信模式以节省电力
-
通信应减少数据传输量以节省网络资源
-
它们通常没有足够的计算资源来启用双向 REST 通信,即在每一侧实现客户端和服务器机制
-
由于存储限制,代码应该有更小的体积
提示
当应用程序特别需要请求-响应架构时,基于 REST 的架构仍然可能是有用的,但大多数基于传感器的硬件应用由于前面的几点限制而受限。
在解决上述问题的其他数据传输范例中,基于发布者/订阅者(pub/sub)的架构脱颖而出。pub/sub 架构使得数据生成节点(发布者)和数据消费节点(订阅者)之间具有双向通信能力。我们将使用 MQTT 作为使用 pub/sub 消息传输模型的协议。让我们首先详细介绍 pub/sub 架构和 MQTT。
MQTT – 一种轻量级消息协议
就像 REST 一样,pub/sub 是最受欢迎的消息模式之一,主要用于在节点之间传输短消息。与部署基于客户端-服务器架构不同,pub/sub 范例实现了称为代理的消息中间件,以接收、排队和转发订阅者和发布者客户端之间的消息:

pub/sub 架构利用基于主题的系统来选择和处理消息,其中每个消息都标记有特定的主题名称。发布者不是直接将消息发送给订阅者,而是首先将带有主题名称的消息发送给代理。在完全独立的过程中,订阅者将对其特定主题的订阅注册到代理。在从发布者接收消息的情况下,代理在将消息转发给注册了该主题的订阅者之前,对该消息执行基于主题的过滤。由于在这个架构中发布者与订阅者松散耦合,发布者不需要知道订阅者的位置,并且可以不间断地工作,无需担心其状态。
在讨论 REST 架构的局限性时,我们注意到它需要在 Arduino 端实现 HTTP 客户端和服务器,以便与 Arduino 进行双向通信。通过展示基于 pub/sub 的代理架构,你只需在 Arduino 上实现轻量级的发布者或订阅者客户端代码,而代理可以在具有更多计算资源的设备上实现。因此,你将无需使用大量资源即可在 Arduino 上启用双向通信。
MQTT 简介
消息队列遥测传输(MQTT)是 pub/sub 范式的非常简单、易于实现且开放的实现。IBM 一直在致力于标准化和支持 MQTT 协议。可以从中获得 MQTT 协议最新规范 v3.1 的文档,官方 MQTT 网站为www.mqtt.org。
作为机器消息的标准,MQTT 被设计成极其轻量级,具有较小的代码占用空间,同时使用较低的带宽进行通信。MQTT 专门设计用于在嵌入式系统上工作,例如携带有限处理器和内存资源的硬件平台,如 Arduino 和其他家电。虽然 MQTT 是一个传输层消息协议,但它使用 TCP/IP 进行网络级连接。由于 MQTT 被设计来支持 pub/sub 消息范式,因此在其硬件应用程序上实现 MQTT 提供了对一对一分布式消息的支持,消除了由 HTTP REST 展示的单向通信限制。由于 MQTT 对有效载荷内容是中立的,因此使用此协议传递的消息类型没有限制。
由于 pub/sub 模式及其在 MQTT 协议中的实现所带来的所有好处,我们将使用 MQTT 协议来完成剩余的练习,以便在 Arduino 和其网络计算机之间进行消息通信。为了实现这一点,我们将使用 MQTT 代理提供消息通信的基础和主题托管,同时在 Arduino 和 Python 端部署 MQTT 发布者和订阅者客户端。
Mosquitto – 一个开源 MQTT 代理
正如我们所描述的,MQTT 只是一个协议标准,它仍然需要软件工具以便在实际应用中实现。Mosquitto 是一个消息代理的开源实现,支持 MQTT 协议标准的最新版本。Mosquitto 代理实现了 MQTT 协议的 pub/sub 模式,同时提供了一个轻量级的机制以实现机器之间的消息传递。Mosquitto 的发展得到了社区力量的支持。Mosquitto 是最受欢迎的 MQTT 实现之一,可在互联网上免费获取并广泛支持。您可以从其网站 www.mosquitto.org 获取有关实际工具和社区的更多信息。
设置 Mosquitto
Mosquitto 的安装和配置过程非常简单。在撰写本书时,Mosquitto 的最新版本是 1.3.4。您也可以在 www.mosquitto.org/download/ 获取有关 Mosquitto 的最新更新和安装信息。
在 Windows 上,您可以简单地下载适用于 Windows 的最新版本安装文件,这些文件是为 Win32 或 Win64 系统制作的。下载并运行可执行文件以安装 Mosquitto 代理。要从命令提示符运行 Mosquitto,您必须将 Mosquitto 目录添加到系统属性的环境变量中的 PATH 变量。在 第一章 “使用 Python 和 Arduino 入门”中,我们全面描述了添加 PATH 变量以安装 Python 的过程。使用相同的方法,将 Mosquitto 安装目录的路径添加到 PATH 值的末尾。如果您使用的是 64 位操作系统,应使用 C:\Program Files (x86)\mosquitto 作为路径。对于 32 位操作系统,应使用 C:\Program Files\mosquitto 作为路径。一旦您将此值添加到 PATH 值的末尾,请关闭任何现有的命令提示符窗口,并打开一个新的命令提示符窗口。您可以通过在新建的窗口中输入以下命令来验证安装。如果一切安装和配置正确,以下命令应无错误执行:
C:\> mosquitto
对于 Mac OS X,使用 Homebrew 工具安装 Mosquitto 是最佳方式。我们已经在第一章中介绍了安装和配置 Homebrew 的过程,即开始使用 Python 和 Arduino。只需在终端中执行以下脚本即可安装 Mosquitto 代理,该脚本将安装 Mosquitto 及其工具,并将它们配置为可以从终端作为命令运行:
$ brew install mosquitto
在 Ubuntu 上,默认仓库已经包含了 Mosquitto 的安装包。根据您使用的 Ubuntu 版本,这个 Mosquitto 版本可能比当前版本要旧。在这种情况下,您必须首先添加此仓库:
$ sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa
$ sudo apt-get update
现在,您可以通过简单地运行以下命令来安装 Mosquitto 包:
$ sudo apt-get install mosquitto mosquitto-clients
熟悉 Mosquitto
由于涉及不同操作系统的多种安装方法,Mosquitto 的初始化可能因您的实例而异。在某些情况下,Mosquitto 可能已经在您的计算机上运行。对于基于 Unix 的操作系统的系统,您可以使用以下命令检查 Mosquitto 是否正在运行:
$ ps aux | grep mosquitto
除非您发现正在运行的代理实例,否则您可以通过在终端中执行以下命令来启动 Mosquitto。执行后,您应该能够看到代理正在运行,同时打印初始化参数和其他发送到它的请求:
$ mosquitto
当您安装 Mosquitto 代理时,安装过程还会安装一些 Mosquitto 工具,包括发布者和订阅者的 MQTT 客户端。这些客户端工具可以用来与任何 Mosquitto 代理通信。
要使用订阅者客户端工具mosquitto_sub,请在终端中使用以下命令,并指定 Mosquitto 代理的 IP 地址。由于我们正在与同一台计算机上运行的 Mosquitto 代理进行通信,您可以避免使用–h <Broker-IP>选项。订阅者工具使用–t选项来指定您计划订阅的主题名称。如您所见,我们正在订阅test主题:
$ mosquitto_sub -h <Broker-IP> -t test
与订阅者客户端类似,发布者客户端(mosquitto_pub)可以用来向特定主题的代理发布消息。正如以下命令所述,您需要使用–m选项后跟一条消息来成功发布。在此命令中,我们正在为test主题发布一条Hello消息:
$ mosquitto_pub -h <Broker-IP> -t test -m Hello
其他重要的 Mosquitto 工具包括mosquitto_password和mosquitto.conf,分别用于管理 Mosquitto 密码文件和设置代理配置。
在 Arduino 和 Python 上开始使用 MQTT
现在你已经在电脑上安装了 Mosquitto 代理,这意味着你有一个实现了 MQTT 协议的工作代理。我们的下一个目标是开发 Arduino 和 Python 中的 MQTT 客户端,以便它们可以作为发布者和订阅者工作。在实现 MQTT 客户端之后,我们将拥有一个完全功能的 MQTT 系统,其中这些客户端通过 Mosquitto 代理进行通信。让我们从在 Arduino 平台上部署 MQTT 开始。
使用 PubSubClient 库在 Arduino 上实现 MQTT
由于 MQTT 是一种基于网络的报文协议,你将始终需要一个以太网盾来与你的网络进行通信。对于接下来的练习,我们将继续使用本章中一直使用的相同硬件。
安装 PubSubClient 库
要使用 Arduino 进行发布/订阅并启用简单的 MQTT 消息传递,你需要 MQTT 的 Arduino 客户端库,也称为 PubSubClient 库。PubSubClient 库帮助你将 Arduino 开发为 MQTT 客户端,然后它可以与运行在电脑上的 MQTT 服务器(在我们的案例中是 Mosquitto 代理)进行通信。由于库只提供创建 MQTT 客户端的方法而不提供代理,与其它消息传递范式相比,Arduino 代码的占用空间相当小。PubSubClient 库广泛使用了默认的 Arduino Ethernet 库,并将 MQTT 客户端实现为 Ethernet 客户端的子类。
要开始使用 PubSubClient 库,你首先需要将库导入到 Arduino IDE 中。从 github.com/knolleary/pubsubclient/ 下载 PubSubClient Arduino 库的最新版本。一旦下载了文件,将其导入到你的 Arduino IDE 中。
我们将使用 PubSubClient 库中安装的一个示例来开始。练习的目标是利用一个基本示例创建一个 Arduino MQTT 客户端,同时进行一些小的修改以适应本地网络参数。然后,我们将使用上一节中学到的 Mosquitto 命令来测试 Arduino MQTT 客户端。同时,确保你的 Mosquitto 代理在后台运行。
开发 Arduino MQTT 客户端
让我们从在 Arduino IDE 菜单中导航到 文件 | 示例 | PubSubClient 来打开 mqtt_basic 示例开始。在打开的程序中,通过更新 mac[] 和 ip[] 变量来更改 Arduino 的 MAC 和 IP 地址值。在上一个章节中,你成功安装并测试了 Mosquitto 代理。使用运行 Mosquitto 的电脑的 IP 地址来更新 server[] 变量:
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x3F, 0x62 };
byte server[] = { 10, 0, 0, 20 };
byte ip[] = { 10, 0, 0, 75 };
如你在代码中所见,我们正在使用服务器的 IP 地址、Mosquitto 端口号和以太网客户端初始化客户端。在使用 PubSubClient 库的任何其他方法之前,你将始终需要使用类似的方法初始化 MQTT 客户端:
EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient);
在代码的进一步部分,我们使用client类上的publish()和subscribe()方法来发布outTopic主题的消息并订阅inTopic主题。你可以使用client.connect()方法指定客户端的名称。如以下代码片段所示,我们将arduinoClient声明为这个客户端的名称:
Ethernet.begin(mac, ip);
if (client.connect("arduinoClient")) {
client.publish("outTopic","hello world");
client.subscribe("inTopic");
}
由于我们在setup()函数中使用此代码,客户端将只发布一次hello world消息——在代码初始化期间——而subscribe方法将由于在 Arduino 的loop()函数中使用client.loop()方法而持续寻找inTopic的新消息:
client.loop();
现在,在后台运行 Mosquitto 的同时,打开另一个终端窗口。在这个终端窗口中,运行以下命令。此命令将使用基于计算机的 Mosquitto 客户端订阅outTopic主题:
$ mosquitto_sub -t "outTopic"
编译你的 Arduino 草图并上传。一旦上传过程完成,你将能够看到打印出的hello world字符串。基本上,一旦 Arduino 代码开始运行,Arduino MQTT 客户端将把hello world字符串发布到outTopic主题的 Mosquitto 代理。在另一边,即 Mosquitto 客户端的一边,你已经启动了mosquitto_sub实用程序,并将接收此消息,因为它已经订阅了outTopic。
尽管你运行了修改后的 Arduino 示例mqtt_basic,你还可以从本章的代码文件夹中找到这个练习的代码。在这个练习中,Arduino 客户端也订阅了inTopic以接收任何为此主题起源的消息。不幸的是,程序不会显示或处理它作为订阅者获得的消息。为了测试 Arduino MQTT 客户端的订阅者功能,让我们打开本章代码文件夹中的mqtt_advanceArduino 草图。
如以下代码片段所示,我们已在callback()方法中添加了显示接收到的代码。当客户端从订阅的主题接收到任何消息时,将调用callback()方法。因此,你可以在callback()方法中实现所有类型的从接收到的消息中实现的功能:
void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived
Serial.print(topic);
Serial.print(':');
Serial.write(payload,length);
Serial.println();
}
在这个mqtt_advanceArduino 草图(sketch)中,我们还把outTopic的发布语句从setup()移动到了loop()函数。这一动作将帮助我们定期发布outTopic的值。将来,我们将扩展此方法以使用传感器信息作为消息,这样其他设备可以通过订阅这些传感器主题来获取这些传感器值:
void loop()
{
client.publish("outTopic","From Arduino");
delay(1000);
client.loop();
}
在更新 mqtt_advance 草稿以适当的网络地址后,编译并将草图上传到你的 Arduino 硬件。为了测试 Arduino 客户端,使用相同的 mosquitto_sub 命令订阅 outTopic。这次,你将在终端上定期收到 outTopic 的更新。为了检查你的 Arduino 客户端的订阅功能,打开你的 Arduino IDE 中的 串行监视器 窗口。一旦 串行监视器 窗口开始运行,请在终端中执行以下命令:
$ mosquitto_pub – t "inTopic" –m "Test"
你可以在 串行监视器 窗口中看到,Test 文本以 inTopic 作为主题名称打印出来。从现在起,你的 Arduino 将同时作为 MQTT 发布者和 MQTT 订阅者。现在让我们开发一个 Python 程序来实现 MQTT 客户端。
使用 paho-mqtt 在 Python 上实现 MQTT
在之前的练习中,我们使用命令行工具测试了 Arduino MQTT 客户端。除非发布的和订阅的消息在 Python 中被捕获,否则我们无法利用它们来开发我们迄今为止构建的所有其他应用程序。为了在 Mosquitto 代理和 Python 解释器之间传输消息,我们使用一个名为 paho-mqtt 的 Python 库。在捐赠给 Paho 项目之前,这个库曾经被称为 mosquitto-python。与 Arduino MQTT 客户端库相同,paho-mqtt 库提供了类似的方法,使用 Python 开发 MQTT pub/sub 客户端。
安装 paho-mqtt
就像我们使用的所有其他 Python 库一样,paho-mqtt 也可以使用 Setuptools 进行安装。要安装库,请在终端中运行以下命令:
$ sudo pip install paho-mqtt
对于 Windows 操作系统,使用 easy_install.exe 来安装库。一旦安装完成,你可以在 Python 交互式终端中使用以下命令来检查库的安装是否成功:
>>> import paho.mqtt.client
使用 paho-mqtt Python 库
paho-mqtt Python 库提供了非常简单的方法来连接到你的 Mosquitto 代理。让我们打开本章代码文件夹中的 mqttPython.py 文件。正如你所看到的,我们通过导入 paho.mqtt.client 库方法初始化了代码:
import paho.mqtt.client as mq
就像 Arduino MQTT 库一样,paho-mqtt 库也提供了连接到 Mosquitto 代理的方法。正如你所看到的,我们通过简单地使用 Client() 方法将我们的客户端命名为 mosquittoPython。该库还提供了用于活动的方法,例如,当客户端收到消息时,on_message,以及发布消息时,on_publish。一旦你初始化了这些方法,你可以通过指定服务器 IP 地址和端口号来将你的客户端连接到 Mosquitto 服务器。
要订阅或发布到某个主题,你只需在客户端实现 subscribe() 和 publish() 方法,具体实现如以下代码片段所示。在本练习中,我们使用 loop_forever() 方法让客户端定期检查代理是否有任何新消息。正如你可以在代码中看到的那样,我们在控制进入循环之前执行了 publishTest() 函数:
cli = mq.Client('mosquittoPython')
cli.on_message = onMessage
cli.on_publish = onPublish
cli.connect("10.0.0.20", 1883, 15)
cli.subscribe("outTopic", 0)
publishTest()
cli.loop_forever()
在你进入循环之前运行所有必需的函数或代码片段非常重要,因为一旦执行 loop_forever(),程序就会与 Mosquitto 服务器进入循环。在此期间,客户端将只执行 on_publish 和 on_message 方法,以处理订阅或发布的主题上的任何更新。
为了克服这种情况,我们正在实施 Python 编程语言的并行多线程范式。虽然我们不会深入探讨多线程,但以下示例将教会你足够的知识来实现基本的编程逻辑。要了解更多关于 Python 线程库和支持的方法,请访问 docs.python.org/2/library/threading.html。
为了更好地理解我们实现的线程方法,请查看以下代码片段。正如你可以在代码中看到的那样,我们使用 Timer() 线程方法每隔 5 秒对 publishTest() 函数进行递归实现。使用此方法,程序将启动一个新的线程,该线程与包含 Mosquitto 循环的主程序线程是分开的。每隔 5 秒,publishTest() 函数将被执行,递归运行 publish() 方法,并最终为 inTopic 发布一条消息:
import threading
def publishTest():
cli.publish("inTopic","From Python")
threading.Timer(5, publishTest).start()
现在,在主线程中,当客户端从订阅的主题接收到新消息时,线程将调用 onMessage() 函数。在当前该函数的实现中,我们只是为了演示目的打印主题和消息。在实际应用中,此函数可以用来实现对接收到的消息的任何操作,例如,将消息写入数据库,运行 Arduino 命令,选择输入,调用其他函数等。简而言之,此函数是任何通过 Mosquitto 代理从订阅的主题接收到的输入的入口点:
def onMessage(mosq, obj, msg):
print msg.topic+":"+msg.payload
同样,每次从第二个线程发布消息时,程序都会执行 onPublish() 函数。就像之前的函数一样,你可以在该函数内实现各种操作,而该函数作为使用此 Python MQTT 客户端发布的任何消息的退出点。在当前 onPublish() 的实现中,我们并没有执行任何操作:
def onPublish(mosq, obj, mid):
pass
在打开的 Python 文件 mqttPython.py 中,您只需更改运行 Mosquitto 代理的服务器的 IP 地址。如果您在相同的计算机上运行 Mosquitto 代理,您可以使用 127.0.0.1 作为本地主机的 IP 地址。在执行此 Python 文件之前,请确保您的 Arduino 正在运行我们在前面练习中创建的 MQTT 客户端。一旦运行此代码,您就可以在 Python 终端中开始看到从您的 Arduino 发送的消息,如下面的截图所示。每当接收到新消息时,Python 程序会打印 outTopic 主题名称,然后是 From Arduino 消息。这证实了 Python 客户端正在接收它订阅的 outTopic 消息。如果您回顾 Arduino 代码,您会注意到它与我们从 Arduino 客户端发布的消息相同。

现在,为了确认 Python MQTT 客户端的发布操作,请从您的 Arduino IDE 中打开 串行监视器 窗口。正如您在 串行监视器 窗口中看到的,包含 inTopic 主题名称和 From Python 消息的文本每 5 秒打印一次。这验证了 Python 发布者,因为我们通过 publishTest() 函数每 5 秒为同一主题发布相同的消息。

练习 4 – Arduino MQTT 网关
在练习 3 中,我们使用了 REST 架构在 Arduino 和网络浏览器之间传输运动和湿度传感器数据。在这个练习中,我们将使用 Mosquitto 代理和 MQTT 客户端开发一个 MQTT 网关,以将传感器信息从我们的 Arduino 传输到网络浏览器。这个练习的目标是复制我们在 REST 练习中实现的相同组件,但使用 MQTT 协议。
如您在系统架构草图中所见,我们有一个连接到我们家庭网络的 Arduino 和以太网盾,而计算机上运行着 Mosquitto 代理和同一网络上的 Python 应用程序。我们使用的是相同的传感器(即,一个运动传感器和一个湿度传感器)以及我们在本章前面练习中使用的相同硬件设计。

在软件架构中,我们有 Arduino 代码,它使用模拟引脚 0 和数字引脚 3 分别与湿度和运动传感器接口。使用PubSubClient库,Arduino 将传感器信息发布到 Mosquitto 代理。在 MQTT 网关上,我们在计算机上运行两个不同的 Python 程序。第一个程序使用paho-mqtt库订阅并从 Mosquitto 代理检索传感器信息,然后将其post到 Web 应用程序。第二个基于web.py的 Python 程序实现 Web 应用程序,同时从第一个 Python 程序获取传感器值。该程序为 MQTT 网关提供了一个用户界面前端。
尽管前面的两个 Python 程序都可以作为单个应用程序的一部分,但我们出于以下原因将与 Mosquitto 通信和通过 Web 应用程序提供信息的任务委托给不同的应用程序:
-
我们希望演示两个库的功能,即
paho-mqtt和web.py,在单独的应用程序中。 -
如果您想在同一个应用程序中运行基于
paho-mqtt和web.py的例程,您将不得不实现多线程,因为这两个例程都需要独立运行 -
我们还希望使用基于 Python 的 REST 方法和
httplib库演示两个 Python 程序之间的信息传输![练习 4 – Arduino 的 MQTT 网关]()
在这个练习中,我们分别用主题标签Arduino/humidity和Arduino/motion对湿度和运动传感器信息进行标记。如果 Arduino 基于的 MQTT 发布者和 Python 基于的 MQTT 订阅者想要通过 Mosquitto 代理传输信息,他们将使用这些主题名称。在我们开始在 Arduino 上实现 MQTT 客户端之前,让我们先在我们的计算机上启动 Mosquitto 代理。
将 Arduino 作为 MQTT 客户端开发
Arduino MQTT 客户端的目标是定期将湿度和运动数据发布到运行在您计算机上的 Mosquitto 代理。从您的代码仓库中的Exercise 4 - MQTT gateway文件夹打开Step1_Arduino.ino草图。像所有其他练习一样,您首先需要更改 MAC 地址和服务器地址值,并为您的 Arduino 客户端分配一个 IP 地址。完成这些修改后,您可以看到我们作为一次性连接消息发布到 Mosquitto 代理的setup()函数,以检查连接。如果您在保持 Mosquitto 连接活跃方面有问题,您可以在定期基础上实现一个类似的功能:
if (client.connect("Arduino")) {
client.publish("Arduino/connection","Connected.");
}
在loop()方法中,我们每 5 秒钟执行一次publishData()函数。它包含发布传感器信息的代码。client.loop()方法还帮助我们保持 Mosquitto 连接活跃,并避免从 Mosquitto 代理超时连接。
void loop()
{
publishData();
delay(5000);
client.loop();
}
如你在下面的代码片段中所见,publishData() 函数获取传感器值,并使用适当的主题标签发布它们。你可能已经注意到,我们在该函数中使用 dtostrf() 函数在发布之前更改数据格式。dtostrf() 函数是默认 Arduino 库提供的一个函数,它将双精度值转换为 ASCII 字符串表示形式。我们还在连续发布传感器数据之间添加了另一个 5 秒的延迟,以避免任何数据缓冲问题:
void publishData()
{
float humidity = getHumidity(22.0);
humidityC = dtostrf(humidity, 5, 2, message_buff2);
client.publish("Arduino/humidity", humidityC);
delay(5000);
int motion = digitalRead(MotionPin);
motionC = dtostrf(motion, 5, 2, message_buff2);
client.publish("Arduino/motion", motionC);
}
完成你想要实现的任何其他修改,然后编译你的代码。如果你的代码编译成功,你可以将其上传到 Arduino 板上。如果你的 Mosquitto 正在运行,你将能够看到一个新的客户端已连接,该客户端名称是你之前在 Arduino 代码中指定的。
使用 Mosquitto 开发 MQTT 网关
你可以将 Mosquitto 代理运行在与 Mosquitto 网关相同的计算机上,或者在你本地网络中的任何其他节点上。为了这个练习,让我们在相同的计算机上运行它。从 Step2_Gateway_mosquitto 文件夹中打开名为 mosquittoGateway.py 的程序文件,该文件夹位于 Exercise 4 - MQTT gateway 文件夹内。网关应用程序的第一个阶段包括基于 paho-mqtt 的 Python 程序,它订阅了 Mosquitto 代理的 Arduino/humidity 和 Arduino/motion 主题:
cli.subscribe("Arduino/humidity", 0)
cli.subscribe("Arduino/motion", 0)
当这个 MQTT 订阅程序从代理接收到消息时,它调用 onMessage() 函数,正如我们在之前的编码练习中所描述的。然后,该方法识别适当的传感器类型,并使用 POST 方法将数据发送到 web.py 程序。我们在这个程序中使用默认的 Python 库 httplib 来实现 POST 方法。在使用 httplib 库时,你必须使用 HTTPConnection() 方法连接到运行在端口号 8080 上的网络应用程序。
注意
虽然这个程序要求你的网络应用程序(第二阶段)必须并行运行,但我们将在接下来的部分中实现这个网络应用程序。确保你首先从下一部分运行网络应用程序;否则,你将遇到错误。
这个库的实现需要你首先将库导入到你的程序中。作为一个内置库,httplib 不需要额外的设置过程:
import httplib
与 Web 应用程序建立连接后,你必须准备在POST方法中发送的数据。httplib方法使用打开的连接上的request()方法来发布数据。你还可以在其他应用程序中使用相同的方法来实现GET功能。发送完数据后,你可以使用close()方法关闭连接。在当前httplib库的实现中,我们是在每个消息上创建和关闭连接。你还可以在onMessage()函数外部声明连接,并在程序终止时关闭它:
def onMessage(mosq, obj, msg):
print msg.topic
connection = httplib.HTTPConnection('10.0.0.20:8080')
if msg.topic == "Arduino/motion":
data = "motion:" + msg.payload
connection.request('POST', '/data', data)
postResult = connection.getresponse()
print postResult
elif msg.topic == "Arduino/humidity":
data = "humidity:" + msg.payload
connection.request('POST', '/data', data)
postResult = connection.getresponse()
print postResult
else:
pass
connection.close()
在执行适当的修改,例如更改 Mosquitto 代理和web.py应用程序的 IP 地址之后,在运行代码之前,前往下一个练习。
使用 web.py 扩展 MQTT 网关
MQTT 网关代码使用基于web.py的 Web 应用程序为用户提供传感器信息。代码与你在练习 3 中实现的内容非常相似。程序文件名为GatewayWebApplication.py,位于你的练习 4 - MQTT 网关代码文件夹中。在这个应用程序中,我们通过简单地实现一个按钮(显示为刷新)来移除了传感器选择过程。此应用程序等待来自前一个程序的POST消息,该消息将在http://<ip-address>:8080/data URL 上接收,最终触发data类。在这个类的POST方法中,将拆分接收到的字符串以识别和更新humidity和motion全局传感器变量的值:
class data:
def POST(self):
global motion, humidity
i = web.input()
data = web.data()
data = data.split(":")
if data[0] == "humidity":
humidity = data[1]
elif data[0] == "motion":
motion = data[1]
else:
pass
return "Ok"
默认 URL http://<ip-address>:8080/ 显示带有刷新按钮的base模板,使用Form()方法填充。如下面的代码片段所示,默认的index类在接收到GET或POST请求时,会渲染带有更新(当前)的humidity和motion值的模板:
class index:
submit_form = form.Form(
form.Button('Refresh',
type="submit",
description='refresh')
)
# GET function
def GET(self):
f = self.submit_form()
return render.base(f, humidity, motion)
# POST function
def POST(self):
f = self.submit_form()
return render.base(f, humidity, motion)
从命令行运行程序。确保你从不同的终端窗口运行这两个程序。
测试你的 Mosquitto 网关
你必须按照指定的顺序执行以下步骤,才能成功执行和测试本练习的所有组件:
-
运行 Mosquitto 代理。
-
运行 Arduino 客户端。如果它已经在运行,请通过关闭 Arduino 客户端并重新启动程序来重新启动程序。
-
在你的终端或命令提示符中执行 Web 应用程序。
-
运行
paho-mqtt网关程序。
如果你遵循这个序列,你的所有程序都将无错误地启动。如果在执行过程中遇到任何错误,请确保你正确地遵循所有指示,同时确认程序中的 IP 地址。要检查你的 Arduino MQTT 客户端,请在 Arduino IDE 中打开串行监视器窗口。你将能够看到传感器信息的周期性发布,如图中所示:

现在,在你的电脑上打开一个网络浏览器,并访问你的网络应用程序的 URL。你应该能看到一个窗口,其外观如下面的截图所示。你可以点击刷新按钮来查看更新的传感器值。

注意
我们在连续的传感器更新之间设置了 5 秒的延迟。从现在起,如果你快速按下刷新按钮,将无法看到更新的值。
在网关程序终端上,每次程序从 Mosquitto 接收新消息时,你都会看到该主题的标签。如果连续传感器更新的延迟不足,且httplib没有足够的时间从web.py应用程序获取响应,程序将使用httplib函数生成错误消息。尽管我们需要额外的延迟来让httplib连续发送数据和接收响应,但当我们使用线程实现核心 Python 代码时,我们将能够避免这种延迟,从而避免在程序之间使用整个POST概念:

通过这个练习,你已经实现了两种不同类型的消息架构,以使用你的家庭网络在 Arduino 和你的电脑或网络应用程序之间传输数据。尽管我们推荐使用以硬件为中心且轻量级的 MQTT 消息范式而不是 REST 架构,但你可以根据应用程序的要求使用这两种通信方法中的任何一种。
摘要
通过 Arduino 连接到计算机网络可以为未来应用程序开发打开无限的可能性。我们以解释重要的计算机网络基础开始本章,同时也涵盖了使 Arduino 能够进行计算机网络连接的硬件扩展。关于启用网络的各种方法,我们首先为 Arduino 建立了一个网络服务器。我们得出结论,由于网络服务器提供的连接数量有限,Arduino 上的网络服务器并不是网络通信的最佳方式。然后我们演示了将 Arduino 作为网络客户端使用,以启用基于 HTTP 的GET和POST请求。尽管这种方法对于基于请求的通信很有用,并且与网络服务器相比资源较少,但由于额外的数据开销,它仍然不是传感器通信的最佳方式。在章节的后期部分,我们描述了一种专为传感器通信设计的轻量级消息协议 MQTT。我们通过几个练习演示了它相对于基于 HTTP 协议的优越性。
在 Arduino 以太网通信的每种方法的帮助下,你学习了用于支持这些通信方法的兼容 Python 库。我们使用了web.py库来使用 Python 开发一个网络服务器,并通过多个示例演示了库的使用。为了支持 MQTT 协议,我们探索了 MQTT 代理,Mosquitto,并使用了 Python 库paho_mqtt来处理 MQTT 请求。
总体来说,在本章中,我们涵盖了 Arduino 和 Python 通信方法的每一个主要方面,并通过简单的练习进行了演示。在接下来的章节中,我们将基于本章学到的基本知识,开发高级 Arduino-Python 项目,这将使我们能够通过互联网远程访问我们的 Arduino 硬件。
第九章:Arduino 与物联网
在上一章中,我们学习了如何从远程位置使用以太网访问 Arduino。主要目标是让你开始使用 Python 开发基于 Arduino 的网络应用。我们能够通过使用各种工具如web.py Python 库、Mosquitto MQTT 代理和 Arduino 以太网库来实现这一点。通过 Python-like 的可扩展语言远程访问传感器数据可以为基于传感器的 Web 应用打开无限可能。近年来,这些应用的快速增长使得物联网(IoT)这一领域的开发成为可能。
在上一章中,我们学习了 Arduino 网络。然而,它仅限于局域网,并且练习的前提条件仅限于你的家庭或办公室。我们甚至没有在练习中涉及互联网来实现全球访问。传统的物联网应用需要通过互联网从世界任何地方远程访问 Arduino。在本章中,我们将通过将 Arduino 与云平台接口来扩展 Arduino 网络概念。我们还将开发 Web 应用程序来访问这些云平台上的传感器数据。在本章的后面部分,我们将介绍设置你的基于云的消息平台的过程,以便提供传感器数据。在本章结束时,你应该能够使用 Arduino、Python 和云来设计和开发全栈物联网应用。
开始使用物联网
在互联网出现之前,基于传感器和执行器的电子控制系统就已经存在于高科技自动化系统中。在这些系统中,传感器通过硬连线连接到微控制器。由于可扩展性的限制,这些系统的覆盖范围在地理上受到限制。这些高科技系统的例子包括工厂自动化、卫星系统、武器系统等。在大多数情况下,这些系统中使用的传感器都很大,微控制器也受到其低计算能力的限制。
随着技术的最新进展,尤其是在半导体行业,传感器和微控制器的物理尺寸显著减小。现在,制造低成本、高效电子组件也成为可能,因此今天开发小型高效基于传感器的硬件产品相对便宜。Arduino 和 Raspberry Pi 是这些成就的绝佳例子。这些基于传感器和执行器的硬件系统与我们所生活的物理世界接口。传感器测量物理环境中的各种元素,而执行器则操纵物理环境。这类基于硬件的电子系统也被称为物理系统。
在另一个方面,半导体行业的进步也促进了高效计算单元的发展,从而推动了个人电脑和网络行业的发展。这一运动导致了全球互联计算机网络,即所谓的网络世界或互联网的形成。每天,都有数以千兆字节的数据在互联网上生成和传输。
物联网领域位于物理和网络安全进步的十字路口,古老的硬连线传感器系统正准备升级到更强大、更高效的系统,这些系统通过互联网高度互联。由于涉及大量传感器,这些系统产生并发送大量数据。这些传感器生成数据已经超过了人类生成数据。
近年来,随着大量消费级物联网产品开始进入市场,物联网已经开始成为一个重要的领域。这些产品包括家庭自动化、医疗保健、活动跟踪、智能能源等领域。物联网领域快速增长的主要原因之一是这些可见解决方案的引入。在许多情况下,这得益于 Arduino 和其他开源硬件平台提供的快速且低成本的原型制作。
到目前为止,在本书中,我们学习了各种传感器接口方法,然后使用这些连接的传感器开发应用程序。在本章中,我们将学习全栈物联网应用开发的最后一步——通过互联网为您的 Python-Arduino 应用程序提供访问权限。现在,让我们首先了解物联网的架构。
物联网 Web 应用架构
在本书的前八章中,我们涵盖了三个主要概念:
-
物理层:我们使用 Arduino 板上的各种传感器和执行器来处理物理环境。例如,温度传感器、湿度传感器和运动传感器用于测量物理现象,而 LED 等执行器则用于改变或产生物理元素。
-
计算层:我们使用 Arduino 草图和 Python 程序将这些物理元素转换为数值数据。我们还利用这些高级语言执行各种计算,如计算相对湿度、开发用户界面、绘制数据并提供 Web 界面。
-
接口层:在我们所涵盖的材料中,我们也使用了各种接口方法来建立 Arduino 和 Python 之间的通信。对于物理层和计算层之间的接口部分,我们使用了串行端口库,通过 REST 和 MQTT 协议建立了基于网络的通信,并开发了 Web 应用。
如您所见,我们已经开发出了具有紧密耦合的物理、计算和接口层的应用。在研究领域,这类应用也被称为网络物理系统。网络物理系统领域广泛使用且流行的术语之一是物联网。尽管与物联网相比,网络物理领域定义得更为详尽,但物联网最近由于涵盖大量子领域(如工业互联网、可穿戴设备、连接设备、智能电网等)而变得更加流行。简单来说,如果一个应用包含处理物理世界的硬件设备,并且具有足够的计算能力以及互联网连接,那么它可以被认定为物联网应用。让我们尝试从我们已经覆盖的材料中理解物联网的架构。
在物理方面,以下图示展示了我们用来处理物理环境的硬件组件。与实际物理世界接口的传感器和执行器可以通过多个低级协议连接到 Arduino。这些组件可以通过 GPIO 引脚以及 I2C 或 SPI 协议进行连接。从这些组件获取的数据会在 Arduino 板上通过用户上传的代码进行处理。尽管 Arduino 代码可以被设计成无需任何外部输入即可执行任务,但在高级应用中,这些来自用户或其他应用的输入是必需的。

作为通信层的一部分,Arduino 可以通过 USB 本地连接到其他计算机。可以通过使用以太网、Wi-Fi 或任何其他无线电通信方法来扩展覆盖范围。
如下图所示,传感器数据通过用于高级处理的计算单元进行收集。这些计算单元强大到足以运行操作系统和编程平台。在这本书中,我们使用了 Python 在计算层开发各种功能。在这一层,我们执行了高级计算任务,例如使用Tkinter库开发图形用户界面,使用matplotlib库绘制图表,以及使用web.py库开发 Web 应用。

在我们之前进行的所有编码练习中,由于硬连接串行接口或本地以太网网络,项目的物理覆盖范围受到限制,如下图所示:

要开发全栈物联网应用程序,我们需要远程访问 Arduino 或将计算层托管在互联网上。在本章中,我们将着手解决这个缺失的环节,并开发各种应用程序,为练习提供互联网连接。为了执行此操作,我们将在第一部分利用商业云平台,并在后面的部分开发我们定制的平台。
由于本章的重点将是云连接,我们不会为每个练习开发硬件电路。我们只会进行一次硬件设计练习,并继续使用相同的硬件进行所有编程练习。同样,我们也将重用上一章中开发的web.py程序,以专注于与 Python 库相关的代码片段,用于开发云应用程序。
硬件设计
让我们从为所有即将进行的练习开发标准硬件开始。我们将需要连接到以太网盾的 Arduino 板来使用以太网协议进行网络连接。在组件方面,你将使用在之前的编码练习中已经使用过的简单传感器和执行器。我们将使用 PIR 运动传感器和 HIH-4030 湿度传感器来分别提供数字和模拟输出。我们还将有一个 LED 作为硬件设计的一部分,这将在编码练习中用作执行器。有关这些传感器的特性和详细解释,你可以参考前面的章节。
要开始组装硬件组件,首先将以太网盾连接到 Arduino 板顶部。将传感器和执行器连接到适当的引脚,如下图所示。一旦组装好硬件,您可以使用以太网线将以太网盾连接到您的家庭路由器。您需要使用 USB 线为板供电,以便从您的计算机上传 Arduino 代码。如果您想将 Arduino 板部署到远程位置,您需要一个外部 5V 电源来为 Arduino 供电。

物联网云平台
术语物联网云平台用于提供非常特定服务、协议支持和基于 Web 的工具的云平台。在更非正式的术语中,这些云物联网平台可以用来上传您的传感器数据,并通过互联网从任何地方访问它们。具有这些基本功能,它们还提供工具,可以在各种平台(如计算机和智能手机)上访问、可视化和处理您的传感器数据。类似的物联网云平台示例包括 Xively (www.xively.com)、2lemetry (www.2lemetry.com)、Carriots (www.carriots.com)、ThingSpeak (thingspeak.com)等。
下图显示了具有基于 Arduino 的传感器系统向云平台发送数据,同时计算单元从云中远程访问数据的物联网系统架构:

Xively 作为最老和最受欢迎的物联网平台,为初学者提供了大量的基于社区的在线帮助。这也是我们选择 Xively 作为即将进行练习的平台的主要原因之一。最近,Xively 更改了创建免费开发者账户的政策,用户现在必须请求访问这个免费账户,而不是免费获得。如果您想使用除 Xively 之外的另一个平台,我们在此节末简要介绍了一些类似平台。
Xively – 一个物联网云平台
Xively 是首批物联网专用云平台之一,它于 2007 年作为 Pachube 成立。它经历了多次名称变更,曾经被称为 Cosm,但现在被称为 Xively。Xively 提供了一个物联网云平台,包括工具和服务,用于开发连接设备、产品和解决方案。正如其网站所述,Xively 是专门为物联网构建的公共云。
在 Xively 上设置账户
现在,我们可以继续为 Xively 平台设置一个新的用户账户。要设置账户,您需要按照以下顺序执行以下步骤:
-
要开始Xively.com的注册过程,请在网页浏览器中打开
xively.com/signup。 -
在注册页面上,您将被提示选择用户名和密码,如下截图所示:
![在 Xively 上设置账户]()
-
在下一页,您将被要求输入一些附加信息,包括您的全名、组织名称、国家、邮政编码、时区等。适当地填写表格,然后点击注册按钮:
![在 Xively 上设置账户]()
-
Xively 将向您在表格中指定的电子邮件账户发送激活邮件。打开邮件并点击激活链接。如果您在收件箱中没有看到邮件,请检查垃圾邮件文件夹。
-
点击激活链接后,您将被重定向到 Xively 网站上的欢迎页面。我们建议您浏览欢迎页面上提供的教程,因为它将帮助您熟悉 Xively 平台。
-
完成教程后,您可以通过
xively.com/login链接从页面返回主用户屏幕。如果您尚未登录,您需要使用电子邮件地址作为用户名和适当的密码登录 Xively 平台。
在 Xively 上工作
Xively 平台允许您创建云设备实例,这些实例可以连接到实际的硬件设备、应用程序或服务。按照以下步骤与 Xively 一起工作:
-
要开始使用 Xively 平台,请从主页添加设备,如图所示:
![与 Xively 一起工作]()
-
一旦点击添加设备按钮,它将提示您进入以下窗口,您将需要提供您要分配的设备名称、描述和隐私状态。在表单中,选择您希望您的开发设备被称作的设备名称,提供简短描述,并将隐私状态选择为私有设备:
![与 Xively 一起工作]()
-
一旦点击添加设备按钮,Xively 将创建一个具有自动生成参数的设备实例,并将您带到开发工作台环境。在您刚刚添加的设备页面中,您可以查看各种标识和安全参数,如产品 ID、序列号、Feed ID、Feed URL和API 端点。在这些参数中,您将经常需要Feed ID信息来完成接下来的练习:
![与 Xively 一起工作]()
-
新创建设备的唯一且安全的 API 密钥也位于页面右侧的侧边栏中。此 API 密钥非常重要,需要像密码一样进行保护,因为任何拥有 API 密钥的人都可以访问设备。
![与 Xively 一起工作]()
-
现在,要远程访问此设备,请打开终端并使用 cURL 命令向其发送数据。在以下命令中,将
<Your_Feed_ID>和<Your_API_key>值替换为您设备可用的值:$ curl --request PUT --data "0,10" --header "X-ApiKey: <Your_API_key" https://api.xively.com/v2/feeds/<Your_Feed_ID>.csv -
如您所见,之前的命令将 10 的值发送到您的设备在 Xively 上的通道 0。执行之前的命令后,您会注意到 Xively 工作台已更新,显示您刚刚使用 cURL 发送的信息:
![与 Xively 一起工作]()
-
尝试使用之前的命令在通道 0 上发送多个值。在 Xively 工作台中,您将能够实时看到由这些值生成的图表。通过在工作台中点击通道 0 来访问图表:
![与 Xively 一起工作]()
使用我们在本例中使用的方法,我们还可以配置 Arduino 自动将传感器值发送到 Xively 平台。这将使 Arduino 数据在 Xively 上的存储和可视化成为可能。
其他物联网平台
在本节中,我们提供了 ThingSpeak 和 Carriots 平台的重要链接。由于我们不会详细覆盖这些平台,这些链接将帮助您找到将 Arduino 和 Python 与 ThingSpeak 和 Carriots 接口的类似示例。
ThingSpeak
以下链接中的教程将帮助您熟悉 ThingSpeak 平台,如果您选择使用它而不是 Xively:
-
官方网站:
thingspeak.com/ -
使用 Arduino 和以太网更新 ThingSpeak 通道:
community.thingspeak.com/tutorials/arduino/using-an-arduino-ethernet-shield-to-update-a-thingspeak-channel/ -
ThingSpeak 的 Arduino 示例:
github.com/iobridge/ThingSpeak-Arduino-Examples -
使用 Python 与 ThingSpeak 通信:
www.australianrobotics.com.au/news/how-to-talk-to-thingspeak-with-python-a-memory-cpu-monitor -
使用 Arduino 和 Python 与 ThingSpeak 通道通信:
vimeo.com/19064691 -
一系列的 ThingSpeak 教程:
community.thingspeak.com/tutorials/
ThingSpeak 是一个开源平台,您可以使用提供的文件创建自己的定制版 ThingSpeak。您可以从 github.com/iobridge/ThingSpeak 获取这些文件和相关指南。
Carriots
Carriots 也为开发者提供免费的基本账户。如果您想将 Carriots 作为 Xively 的替代品使用,请使用以下链接中的教程开始:
-
官方网站:
www.carriots.com/ -
在 Carriots 上设置账户:
learn.adafruit.com/wireless-gardening-arduino-cc3000-wifi-modules/setting-up-your-carriots-account -
Arduino 的 Carriots 库:
github.com/carriots/arduino_library -
Arduino 的 Carriots 示例:
github.com/carriots/arduino_examples -
将 Carriots 连接到 Python 网络应用:
www.instructables.com/id/Connect-your-Carriots-Device-to-Panics-Status-Boa/
使用 Python 和 Xively 开发云应用
现在,您已经对可用的商业物联网平台有了基本的了解,您可以根据自己的舒适度和需求选择一个。由于本章的目标是让您熟悉将云平台与 Python 和 Arduino 集成,因此很难全面解释每个云平台及其实际示例。因此,我们将使用 Xively 作为后续集成练习的默认物联网云平台。
现在您已经知道如何在 Xively 上创建账户并使用 Xively 平台,是时候开始将真实硬件与 Xively 平台接口了。在本节中,我们将介绍上传和下载 Xively 数据的方法。我们将结合我们构建的 Arduino 硬件和 Python 程序来向您展示与 Xively 通信的基本方法。
将 Arduino 与 Xively 接口
与 Xively 建立通信的第一阶段包括通过独立 Arduino 代码将 Arduino 板与 Xively 平台接口。我们已经使用 Arduino Uno、以太网盾和几个传感器构建了必要的硬件。让我们通过 USB 端口将其连接到您的计算机。您还需要使用以太网线将以太网盾连接到您的家庭路由器。
上传 Arduino 数据到 Xively
Arduino IDE 有一个内置的示例,可以用来与 Xively 服务通信。这被称为PachubeClient(Pachube 是 Xively 之前的名称)。
注意
需要注意的是,使用此默认示例的原因是为了在接口练习中给您一个快速入门。这个特定的草图相当陈旧,可能会在 Arduino IDE 的即将发布的版本中被删除作为默认练习。在这种情况下,您可以直接跳到下一个练习或开发您自己的草图以执行相同的练习。
按照以下步骤上传 Arduino 数据到 Xively:
-
打开 Arduino IDE,然后通过导航到文件 | 示例 | 以太网 | PachubeClient来打开PachubeClient示例。
-
要与 Xively 建立通信,您需要 Xively 设备的 feed ID 和 API 密钥,这些信息您在上一个部分中已获得。
-
在打开的 Arduino 草图中进行以下更改,使用获得的 feed ID 和 API 密钥。您可以指定任何项目名称作为
USERAGENT参数:#define APIKEY "<Your-API-key>" #define FEEDID <Your-feed-ID> #define USERAGENT "<Your-project-name>" -
在 Arduino 草图中,您还必须更改以太网盾的 MAC 地址和 IP 地址。您应该熟悉从上一章中执行的活动中获得这些地址。使用这些值并相应地修改以下代码行:
byte mac[] = {0x90, 0xA2, 0xDA, 0x0D, 0x3F, 0x62}; IPAddress ip(10,0,0,75); -
由于打开的 Arduino 示例是为 Pachube 创建的,因此您需要将服务器地址更新为以下代码片段中指定的
api.xively.com。注释掉 IP 地址行,因为我们不再需要它,并添加server[]参数://IPAddress server(216,52,233,122); char server[] = "api.xively.com"; -
在
sendData()函数中,将通道名称更改为HumidityRaw,因为我们已经将 HIH-4030 湿度传感器连接到模拟端口。我们目前不进行任何相对湿度计算,并将仅上传传感器的原始数据:// here's the actual content of the PUT request: client.print("HumidityRaw,"); client.println(thisData); -
完成这些更改后,从包含本章代码的文件夹中打开
XivelyClientBasic.ino文件。将它们与您的当前草图进行比较,并在一切看起来满意的情况下编译/上传草图到 Arduino 板。一旦上传了代码,在 Arduino IDE 中打开串行监视器窗口以观察以下输出:![将 Arduino 数据上传到 Xively]()
-
如果您在串行监视器窗口中看到与之前截图相似的输出,则表示您的 Arduino 已成功连接到 Xively,并在 HumidityRaw 通道上上传数据。
-
在 Xively 网站上打开您的设备,您将在网页上看到类似于以下截图的输出。这证实您已成功使用远程位置的 Arduino 将数据上传到物联网云平台:
![将 Arduino 数据上传到 Xively]()
从 Xively 下载数据到 Arduino
在之前的编码练习中,我们使用默认的 Arduino 示例与 Xively 通信。然而,Xively 还提供了一个非常高效的 Arduino 库,其中内置了用于快速编程的功能。在下一个练习中,我们将使用Xively-Arduino库的替代方法与 Xively 平台通信。虽然您可以使用这两种方法中的任何一种,但我们建议您使用Xively-Arduino库,因为它是由 Xively 官方维护的。
在这个练习中,我们将从名为 LED 的通道下载数字值。稍后,我们将使用这些数字值,0 和 1,来切换连接到我们的 Arduino 板上的 LED。作为此通道的输入,我们将在 Xively 平台网站上更改通道的当前值,同时让 Arduino 下载该值并执行适当的任务。
让我们从导入Xively-Arduino库及其依赖项开始。如您所知,如何在 Arduino IDE 中导入库,请访问github.com/amcewen/HttpClient下载并导入HttpClient库。这是Xively-Arduino库运行所必需的依赖项。
在导入HttpClient库后,从github.com/xively/xively_arduino下载Xively-Arduino库并重复导入过程。
Xively-Arduino库附带了一些示例,以便您开始使用。我们将使用他们的示例作为下载数据的基础代码。
-
在 Arduino IDE 中,导航到文件 | 示例 | Xively_arduino | DatastreamDownload并打开DatastreamDownload示例。将默认 API 密钥更改为从您创建的设备获得的自己的 API 密钥。如以下代码片段所示,您还需要确定您的通道名称,在本例中为 LED:
char xivelyKey[] = "<Your-API-key>"; char ledId[] = "LED"; -
Xively-Arduino库要求您将XivelyDatastream变量定义为数组。您也可以根据您的应用程序指定多个数据流:XivelyDatastream datastreams[] = { XivelyDatastream(ledId, strlen(ledId), DATASTREAM_FLOAT), }; -
您还需要使用
XivelyFeed函数声明一个名为feed的变量。如以下代码行所示,将默认的 feed ID 替换为适当的 ID。在feed变量的初始化中,值1代表XivelyDatastream数组中的datastreams数量:XivelyFeed feed(<Your-feed-ID>, datastreams, 1); -
在我们的练习中,我们想要定期检索 LED 通道的值并根据实际情况打开或关闭 LED。在以下代码片段中,我们从
feed[0]获取浮点值,其中0指定位于datastreams数组中0位置的data stream:Serial.print("LED value is: "); Serial.println(feed[0].getFloat()); if (feed[0].getFloat() >= 1){ digitalWrite(ledPin, HIGH); } else{ digitalWrite(ledPin, LOW); } -
如您现在所知,此练习需要更改参数,请从代码文件夹中打开
XivelyLibBasicRetrieveData.inoArduino 草图。此草图包含您进行练习所需的精确代码。尽管此草图包括必要的修改,但您仍然需要更改特定于账户的参数值,即 API 密钥、feed ID 等。在您上传此草图之前,请转到 Xively 平台并创建一个名为LED的通道,当前值为1,如以下截图所示:![从 Xively 下载数据到 Arduino]()
-
现在,将代码编译并上传到您的 Arduino。
-
一旦您将编译后的代码上传到 Arduino,打开串行监视器窗口并等待一个类似于以下截图的输出。您会注意到 Arduino 硬件上的 LED 灯已点亮:
![从 Xively 下载数据到 Arduino]()
-
您可以回到 Xively LED 通道并将当前值字段更改为
0。几秒钟后,您会注意到 Arduino 硬件上的 LED 灯已关闭。通过这个练习,您已成功在 Arduino 和 Xively 平台之间建立了双向通信。
用于使用 Arduino 上传和下载数据的先进代码
在前两个 Arduino 练习中,我们分别执行了上传和下载任务。在这个练习中,我们想要创建一个 Arduino 程序,我们可以从连接的传感器(PIR 运动传感器和 HIH-4030 湿度传感器)上传数据,同时检索控制 LED 的值。打开 Arduino 草图,XivelyLibAdvance.ino,其中包含演示这两种功能的代码。正如你在以下代码片段中可以看到的,我们为每个组件定义了三个独立的通道,同时为上传(datastreaU[])和下载(datastreamD[])创建了独立的XivelyDatastream对象。同样,我们也创建了两个不同的 feed,feedU和feedD。将上传和下载任务委托给不同对象的主要原因是独立更新 LED 通道的值,同时上传HumidityRaw和MotionRaw通道的数据流:
char ledId[] = "LED";
char humidityId[] = "HumidityRaw";
char pirId[] = "MotionRaw";
int ledPin = 2;
int pirPin = 3;
XivelyDatastream datastreamU[] = {
XivelyDatastream(humidityId, strlen(humidityId), DATASTREAM_FLOAT),
XivelyDatastream(pirId, strlen(pirId), DATASTREAM_FLOAT),
};
XivelyDatastream datastreamD[] = {
XivelyDatastream(ledId, strlen(ledId), DATASTREAM_FLOAT),
};
XivelyFeed feedU(<Your-feed-ID>, datastreamU, 2);
XivelyFeed feedD(<Your-feed-ID>, datastreamD, 1);
在 Arduino 代码的loop()函数中,我们定期从feedD获取 LED 通道的当前值,然后执行 LED 动作:
int retD = xivelyclient.get(feedD, xivelyKey);
Serial.print("xivelyclient.get returned ");
在周期函数的第二阶段,我们从 Arduino 板上的模拟和数字引脚获取原始传感器值,然后使用feedU上传这些值:
int humidityValue = analogRead(A0);
datastreamU[0].setFloat(humidityValue);
int pirValue = digitalRead(pirPin);
datastreamU[1].setFloat(pirValue);
int retU = xivelyclient.put(feedU, xivelyKey);
Serial.print("xivelyclient.put returned ");
对代码进行适当的修改以适应 feed ID 和 API 密钥,然后将草图上传到 Arduino 板。一旦将这个 Arduino 草图上传到你的平台,你应该能在串行监视器窗口看到以下输出。现在你可以从 USB 端口断开 Arduino,并连接外部电源。现在,你已经使用以太网线将 Arduino 组件连接到你的本地网络,你可以将 Arduino 组件放置在你工作场所的任何位置。

Python – 将数据上传到 Xively
与我们如何将 Arduino 连接到 Xively 类似,我们现在将探索通过 Python 连接 Xively 平台的方法,从而完成循环。在本节中,我们将关注使用 Python 上传数据到 Xively 的不同方法。我们将从一个与 Xively 通信的基本方法开始,并进一步使用web.py来实现通过 Web 应用程序的接口。
首先,让我们使用以下命令在你的计算机上安装 Xively 的 Python 库,xively-python:
$ sudo pip install xively-python
发送数据的基本方法
再次提醒,你需要你的虚拟设备在 Xively 平台上创建的 API 密钥和 feed ID。Python,借助xively-python库,提供了非常简单的方法来与 Xively 平台建立通信通道。从你的代码文件夹中,打开uploadBasicXively.py文件。根据代码中的说明,将FEED_ID和API_KEY变量替换为适当的 feed ID 和 API 密钥:
FEED_ID = "<Your-feed-ID>"
API_KEY = "<Your-API-key>"
使用XivelyAPIClient方法创建一个api实例,并通过api.feeds.get()方法创建feed变量:
api = xively.XivelyAPIClient(API_KEY)
feed = api.feeds.get(FEED_ID)
就像我们在 Arduino 练习中所做的那样,您需要从数据源中为每个通道创建数据流。如下代码片段中指定,尝试从数据源中获取指定的通道,或者如果它不在 Xively 虚拟设备上,则创建一个新通道。在创建新通道时,您也可以指定标签和其他变量:
try:
datastream = feed.datastreams.get("Random")
except HTTPError as e:
print "HTTPError({0}): {1}".format(e.errno, e.strerror)
datastream = feed.datastreams.create("Random", tags="python")
print "Creating 'Random' datastream"
一旦您为通道打开了数据流,您可以使用datastream.current_value方法指定当前值,并更新该值,这将把此值上传到指定的通道:
datastream.current_value = randomValue
datastream.at = datetime.datetime.utcnow()
datastream.update()
一旦您对uploadBasicXively.py文件进行了指定的修改,请使用以下命令执行它:
$ python uploadBasicXively.py
打开 Xively 网站上的虚拟设备,以找到填充了您上传的数据的Random通道。它看起来类似于以下截图:

基于 web.py 的 Web 界面上传数据
在上一章中,我们在开发模板和 Web 应用时使用了web.py库。在本练习中,我们将利用我们在上一练习中创建的web.py表单和 Xively 代码中的程序。本练习的目标是使用 Web 应用将数据发送到 LED 通道,同时观察 Arduino 硬件上 LED 的行为。
您可以在本章文件夹中找到本练习的 Python 程序,文件名为uploadWebpyXively.py。正如您在代码中看到的,我们正在使用web.py表单获取两个输入,Channel和Value。我们将使用这些输入来修改 LED 通道的当前值:
submit_form = form.Form(
form.Textbox('Channel', description = 'Channel'),
form.Textbox('Value', description = 'Value'),
form.Button('submit', type="submit", description='submit')
)
模板文件base.html也被修改以适应本练习所需的微小变化。正如您在打开的 Python 文件中看到的,我们正在使用与上一练习中用于与 Xively 接口相同的代码。唯一的重大修改是对datastream.update()方法的修改,现在它被放置在POST()函数中。当您提交表单时,将执行此方法。一旦您更改了文件中的 API 密钥和 feed ID,请执行 Python 代码,并在您的 Web 浏览器中打开http://localhost:8080。您可以看到正在运行的 Web 应用,如下面的截图所示。输入图中显示的值以在 Arduino 板上打开 LED。您可以将Value参数更改为0以关闭 LED。

Python – 从 Xively 下载数据
从 Xively 下载数据的过程包括请求指定通道的当前值参数。在下一个练习中,我们将开发一个参考代码,该代码将在下一个下载练习中使用。在那个练习中,我们将开发一个高级 Web 应用程序,用于从特定的 Xively 通道检索数据。
由于我们使用基于 REST 协议的函数与 Xively 进行通信,Xively 不会简单地通知您任何新的、可用的更新,相反,您将不得不请求它。在此阶段,重要的是要注意,我们将不得不定期从 Xively 请求数据。然而,Xively 提供了一个称为触发器的替代方法来克服这个问题,这将在本节后面进行解释。
从 Xively 获取数据的基本方法
就像上传练习一样,下载练习也需要类似的代码来实例化XivelyAPIClient()和api.feeds.get()方法。由于我们是检索数据而不是发送它,我们只会使用feed.datastreams.get()方法并避免使用feed.datastreams.create()方法。下载过程需要通道已经存在,这也是我们为什么只需要使用get()方法的主要原因:
try:
datastream = feed.datastreams.get("Random")
except HTTPError as e:
print "HTTPError({0}): {1}".format(e.errno, e.strerror)
print "Requested channel doesn't exist"
一旦初始化了datastream对象,就可以使用datastream.current_value方法获取通道的最新可用值:
latestValue = datastream.current_value
要启用完整代码执行此练习,请打开downloadXivelyBasic.py代码,并将馈送 ID 和 API 密钥的值更改为适当的值。在这个练习中,我们正在使用在上传练习中创建的Random通道。在您执行此 Python 代码之前,您需要执行uploadXivelyBasic.py文件,该文件将连续为Random通道提供随机数据。现在,您可以执行downloadXivelyBasic.py文件,该文件将定期(由sleep()函数指定的延迟)获取Random通道的当前值。如图所示,我们每 10 秒就会为Random通道获取一个新的值:

从 web.py Web 界面检索数据
这是一个高级练习,我们将从另一个 Xively 通道获取数据后,将其上传到 Xively 的一个通道,并使用通过 Web 表单输入的数据进行处理。正如您所知,连接 HIH-4030 传感器的模拟引脚提供了原始传感器值,而相对湿度取决于当前温度的值。在这个练习中,我们将开发一个 Web 应用程序,以便用户可以手动输入温度值,我们将使用这个值从原始传感器数据中计算相对湿度。
在我们开始详细讲解代码之前,首先打开uploadWebpyXively.py文件,更改适当的参数,并执行该文件。现在,在网页浏览器中打开http://localhost:8080位置。你将能够看到一个要求你提供当前温度值的网页应用程序。同时,在做出适当更改后,将XivelyLibAdvance.ino草图上传到 Arduino 板。使用这个程序,Arduino 将开始向MotionRaw和HumidityRaw通道发送原始运动和湿度值。在运行的网页应用程序中,提交带有自定义温度值的表单,你将能够看到网页应用程序加载当前相对湿度(以百分比为单位)。在内部,当你提交表单时,网页应用程序从HumidityRaw通道检索当前原始湿度值,执行relativeHumidity(data, temperature)函数,将计算出的湿度值上传到名为Humidity的新通道,然后在网页应用程序中显示该值。

如果你在一个网页浏览器上打开你的 Xively 平台页面,你将能够看到一个新创建的Humidity通道,显示当前相对湿度的值。你可以在网页应用程序中提交多个温度值,以查看在Humidity通道的图表上反映的结果,如以下截图所示。尽管这个练习演示了一个单一用例,但这个网页应用程序可以通过多种方式扩展,以创建复杂的应用程序。

触发器 – 来自 Xively 的自定义通知
Xively 平台主要基于 REST 协议部署服务,该协议没有提供在更新为新值时自动发布数据的条款。为了克服这一限制,Xively 实现了触发器的概念,它提供了除发布数据之外的功能。通过这种方式,你可以为任何通道创建一个触发器,当满足该触发器设定的条件时,在指定位置执行POST操作。例如,你可以在Humidity通道上设置一个触发器,当湿度值变化时(即增加或减少到给定阈值以上或以下)向你发送通知。你只需点击以下截图所示的添加触发器按钮,就可以在你的 Xively 平台账户中创建一个触发器:

在创建触发器时,您可以指定要监控的通道以及触发指定 HTTP POST URL 通知的条件。如图所示,在保存触发器之前,完成通道、条件和HTTP POST URL的信息。这种方法的重大缺点是 Xively 需要实际的 URL 来发送POST通知。如果您的当前计算机没有静态 IP 地址或 DNS 地址,触发器将无法向您发送通知:

您自己的物联网云平台
在上一节中,我们使用了一个同时提供受限免费访问基本功能的商业物联网平台。我们还学习了与基于 REST 协议的 Xively 进行通信的各种方法。对于任何小型项目或原型,Xively 和其他类似的物联网平台提供了一个足够的解决方案,因此我们推荐使用。然而,Xively 提供的有限免费服务可能无法满足您开发全栈物联网产品的所有需求。以下是一些您可能想要配置或开发自己的物联网平台的情况:
-
开发您自己的商业物联网平台
-
开发仅属于您产品的自定义功能
-
在增加更多控制功能和通信协议的同时,也要确保您的数据安全
-
需要一个经济实惠的解决方案来处理大规模项目
本节将指导您逐步创建一个基本的低级别物联网云平台。本节的目标是使您熟悉创建物联网平台的要求和过程。要开发像 Xively 这样的大规模、多样化且功能丰富的平台,您需要在云和分布式计算领域拥有大量的知识和经验。不幸的是,云和分布式计算超出了本书的范围,我们将坚持实现基本功能。
要开发一个可以通过互联网访问的云平台,您至少需要一个具有互联网连接的计算单元和一个静态 IP 或 DNS 地址。如今,大多数面向消费者的互联网服务提供商(ISPs)在其互联网服务中不提供静态 IP 地址,这使得在家托管服务器变得困难。然而,像亚马逊、谷歌和微软这样的各种公司提供免费或成本效益高的云计算服务,这使得在它们的平台上托管云变得更加容易。这些服务具有高度的扩展性,并配备了大量功能,以满足大多数消费者的需求。在接下来的部分中,您将创建您的第一个云计算实例在亚马逊网络服务(AWS)上。在本章的后面部分,我们将安装和配置适当的软件工具,如 Python、Mosquitto 代理等,以利用这个亚马逊实例作为物联网云平台。
注意
开发或配置个人云平台的主要原因是可以通过互联网访问您的物联网硬件。由于您的家庭网络没有静态 IP 地址,您可能无法从远程位置访问您的原型或项目。云平台可以用作网络项目的事实上的计算单元。
熟悉亚马逊 AWS 平台
AWS 是亚马逊提供的一系列各种云服务,共同构成了一个云计算平台。AWS 提供的一种原始且最受欢迎的服务是其弹性计算云(EC2)服务。EC2 服务允许用户从其庞大的云基础设施中创建具有不同计算能力和操作系统组合的虚拟机实例。这些虚拟实例的计算属性也极其容易在任何时候更改,使其具有高度的扩展性。当您尝试使用 EC2 创建自己的物联网平台时,这种可扩展性功能将极大地帮助您,因为您可以根据需求扩展或压缩实例的大小。如果您不熟悉云计算的概念或 AWS 作为特定产品,您可以从aws.amazon.com了解更多信息。
EC2 云平台与 Xively 不同,因为它提供通用云实例、虚拟机,具有计算能力和存储,可以通过安装和配置特定平台的软件将其转换为任何特定功能的平台。需要注意的是,您真的不需要成为云计算专家才能进一步学习本章内容。接下来的部分提供了一个直观的指南,以执行基本任务,例如设置账户、创建和配置您的虚拟机,以及安装软件工具以创建物联网平台。
在 AWS 上设置账户
亚马逊为基于云的虚拟机的基本实例提供一年的免费访问。此实例每月包括 750 小时的免费使用时间,这超过了任何一个月的小时数,因此整个月都是免费的。AWS 账户的数据存储容量和带宽足以满足基本的物联网或 Arduino 项目。要在亚马逊的 AWS 云平台上创建一年的免费账户,请执行以下步骤:
-
打开
aws.amazon.com并点击要求您免费试用 AWS 或类似文本的按钮。 -
此操作将带您到一个登录或创建 AWS 账户页面,如下所示截图。当您选择我是新用户选项时,输入您想要用于此账户的电子邮件地址,然后点击使用我们的安全服务器登录按钮。如果您已经有 AWS 账户并且知道如何在亚马逊 AWS 上创建账户,您可以使用那些凭据并跳到下一部分:
![在 AWS 上设置账户]()
注意
亚马逊只为每个账户允许一个免费实例。如果您是现有的 AWS 用户并且您的免费实例已经被另一个应用程序占用,您可以使用相同的实例来容纳 MQTT 代理或购买另一个实例。
-
在下一页,您将被提示输入您的姓名、电子邮件地址和密码,如下所示截图。填写信息以继续注册过程:
![在 AWS 上设置账户]()
-
在注册过程中,您将被要求输入您的信用卡信息。但是,您不会因为使用免费账户中包含的服务而被收费。只有在您超出任何限制或购买任何附加服务时,您的信用卡才会被使用。
-
下一个阶段包括使用您的手机号码验证您的账户。按照以下截图显示的说明完成身份验证过程:
![在 AWS 上设置账户]()
-
一旦您验证了您的身份,您将被重定向到列出可用亚马逊 AWS 计划的页面。选择您想要订阅的适当计划并继续。如果您不确定,可以选择基础(免费)计划选项,我们推荐您为此目的使用该选项。如果您想升级当前计划,亚马逊管理控制台页面将允许您选择其他计划。
-
启动亚马逊管理控制台。
由于您现在有了亚马逊 AWS 账户,让我们在它上面创建您的虚拟实例。
在 AWS EC2 服务上创建虚拟实例
为了在亚马逊的 EC2 平台上创建虚拟实例,首先使用您的凭证登录 AWS 并打开管理控制台。接下来,点击EC2标签并按以下步骤逐步执行:
-
在EC2 控制台页面,转到创建实例并点击启动实例按钮。这将打开一个向导,引导您完成设置过程:
![在 AWS EC2 服务上创建虚拟实例]()
-
在向导的第一页,您将被提示为您的虚拟实例选择一个操作系统。选择如图所示的Ubuntu Server 14.04 LTS,它符合免费层级的资格。为了避免使用高级实例产生任何费用,请确保您选择的选项符合免费层级的资格:
![在 AWS EC2 服务上创建虚拟实例]()
-
在下一个窗口中,您将看到一个包含不同计算能力配置选项的列表。从通用型系列中选择t2.micro类型,它符合免费层级的资格。t2.micro层提供的计算能力足以完成我们在书中将要进行的练习,以及大多数 DIY 项目。请确保除非您对自己的选择有信心,否则不要选择其他层级。
![在 AWS EC2 服务上创建虚拟实例]()
-
一旦您选择了指定的层级,请点击审查和启动按钮来审查实例的最终配置。
-
审查配置并确保您已选择了前面提到的适当选项。现在,您可以点击启动按钮继续下一步。
-
这将打开一个弹出窗口,提示您创建一个新的密钥对,该密钥对将在接下来的步骤中进行身份验证:
![在 AWS EC2 服务上创建虚拟实例]()
-
如前一个截图所示,在第一个下拉菜单中选择创建新的密钥对,并为密钥对提供一个名称。点击下载密钥对按钮下载密钥。下载的密钥将具有您在之前选项中提供的名称,并带有
.pem扩展名。如果您已经有了现有的密钥,您可以从第一个下拉菜单中选择适当的选项。每次您想要登录到此实例时都需要这个密钥。请将此密钥保存在安全的地方。 -
再次点击启动实例按钮,最终启动实例。您的虚拟实例现在已在 AWS 上启动,并在 EC2 中运行。
-
现在,点击查看实例按钮,这将带您回到 EC2 控制台窗口。您将能够在列表中看到您最近创建的
t2.micro实例。 -
要了解更多关于您的虚拟实例的详细信息,从列表中选择它。一旦您选择了您的实例,您将在底部标签中看到更多信息。这些信息包括公共 DNS、私有 DNS、公共 IP 地址等。
![在 AWS EC2 服务上创建虚拟实例]()
-
保存此信息,因为您将需要它来登录您的实例。
现在,您已成功创建并开启了使用亚马逊 AWS 的虚拟云实例。然而,此实例正在亚马逊 EC2 上运行,您将需要远程认证进入此实例以访问其资源。
登录您的虚拟实例
实际上,您的虚拟实例是在云上的虚拟计算机,具有与您的普通计算机类似的计算资源。您现在需要登录到正在运行的虚拟实例以访问文件、运行脚本和安装额外的包。为了建立安全的认证和访问程序,您需要使用安全外壳(SSH)协议,并且有多个方法可以从您的计算机使用 SSH。如果您使用 Mac OS X 或 Ubuntu,SSH 客户端程序已包含在您的操作系统中。对于 Windows,您可以从www.putty.org/下载 PuTTY SSH 客户端。
从 EC2 管理窗口中检索您实例的公共 IP 地址。要在 Linux 或 Mac 环境中使用默认的 SSH 客户端,请打开终端并导航到您保存具有.pem扩展名的密钥文件的文件夹。在终端窗口中,执行以下命令以使您的密钥可访问:
$ chmod 400 test.pem
一旦您更改了密钥文件的权限,运行以下命令以登录虚拟实例。在命令中,您必须将<key-name>替换为您的密钥文件名,将<public-IP>替换为从管理控制台检索的公共 IP:
$ ssh –i <key-name>.pem ubuntu@<public-IP>
执行此命令后,如果您是首次认证实例,您将被要求继续连接过程。在提示符下,键入yes并按Enter键继续。认证成功后,您将在同一终端窗口中看到您的虚拟实例的命令提示符。
如果您正在使用 Windows 操作系统并且不确定 SSH 客户端的状态,请在 EC2 窗口中选择您的实例,然后点击顶部导航栏中的连接按钮,如下截图所示:

此操作将打开一个弹出窗口,其中包含一个简短的教程,解释连接过程。此教程还链接到 PuTTY 的逐步认证指南。
在 EC2 实例上创建物联网平台
由于您已成功设置亚马逊 EC2 实例,您拥有一个在云中运行的虚拟计算机,并具有静态 IP 地址以实现远程访问。然而,此实例不能被归类为物联网平台,因为它只包含一个纯操作系统(在我们的案例中是 Ubuntu Linux)并且缺少必要的软件包和配置。
在您的虚拟实例上设置自定义物联网云平台有两种不同的方法:
-
设置开源物联网平台,如 ThingSpeak
-
单独安装和配置所需的软件工具
在设置开源物联网平台时,请注意以下要点:
-
ThingSpeak 是开源物联网平台之一,它提供支持文件以创建和托管您自己的 ThingSpeak 平台副本。
-
在您的 AWS 实例上设置此平台相当简单,您可以通过
github.com/iobridge/ThingSpeak获取安装所需的文件和指南。 -
尽管这个个性化的 ThingSpeak 平台将提供足够的工具来开始开发物联网应用程序,但平台的功能将限于提供的功能集。为了完全控制定制,您可能需要使用下一个选项。
如果您想单独安装和配置必要的软件工具,以下是需要记住的:
-
此选项包括提供特定项目所需的软件工具,如 Python 和 Mosquitto 代理,以及所需的 Python 库,如
web.py和paho_mqtt。 -
我们已经与基于 Mosquitto 代理和
web.py的应用程序实现相关的练习工作。这个定制的物联网云平台版本可以减少安装额外开源平台工具的复杂性,同时仍提供必要的支持以托管应用程序。 -
Arduino 程序可以直接使用 REST 或 MQTT 协议与这个自定义平台进行通信。它还可以作为远程计算单元与 Xively 或其他第三方物联网云平台进行通信。
在下一节中,我们将通过在您的虚拟实例上安装 Mosquitto 代理和必要的软件包来开始平台部署过程。这将随后是配置虚拟实例以支持 MQTT 协议。一旦您的物联网云平台启动并运行,您只需从实例中运行上一章的基于 Python 的 Mosquitto 代码,进行少量或无修改即可。在未来,包含 Mosquitto 代理和 Python 项目的这个物联网平台可以扩展以适应额外的功能、协议和额外的安全性。
在 AWS 上安装必要的软件包
使用 SSH 协议和密钥对登录您的虚拟实例。一旦您处于命令提示符,您需要执行的第一项任务是更新 Ubuntu 中所有过时的软件包,这是您的虚拟实例的操作系统。依次执行以下命令:
$ sudo apt-get update
$ sudo apt-get upgrade
Ubuntu 已经预装了最新的 Python 版本。但是,您仍然需要安装 Setuptools 来安装额外的 Python 软件包:
$ sudo apt-get install python-setuptools
Ubuntu 的软件仓库也托管了 Mosquitto,可以直接使用以下命令安装。使用此命令,我们将一起安装 Mosquitto 代理、Mosquitto 客户端以及所有其他依赖项。在安装过程中,你将被要求确认安装额外的包。在终端输入Yes并继续安装:
$ sudo apt-get install mosquitto*
现在你已经在你的虚拟实例上安装了 Mosquitto 代理,你可以通过执行 Mosquitto 命令来运行它。为了开发基于 Python 的 Mosquitto 应用,我们需要在我们的实例上安装 Python Mosquitto 库。让我们使用以下命令通过 Setuptools 安装库:
$ sudo easy_install pip
$ sudo pip install paho_mqtt
在上一章中,我们开发了一个基于web.py的 Web 应用,该应用利用paho_mqtt库来支持 MQTT 协议。与第一个项目一样,我们将在基于 EC2 的虚拟实例上部署相同的 Web 应用,以展示你的自定义物联网云平台。作为此项目的依赖项,你首先需要web.py Python 库,你可以使用以下命令安装:
$ sudo pip install web.py
现在你已经拥有了运行物联网应用所需的所有软件包。为了使你的 Web 应用可以通过互联网访问,你需要配置你的虚拟实例的安全设置。
配置虚拟实例的安全设置
首先,我们将配置虚拟实例以安全地托管 Mosquitto 代理。稍后,我们将介绍设置基本安全性的方法,以防止自动化机器人或垃圾邮件尝试滥用你的 Mosquitto 服务器。
要更改你虚拟实例上的任何参数,你将不得不使用AWS 管理控制台页面网络与安全部分的安全组工具。打开安全组部分,如下截图所示:

每个虚拟实例都有一个默认的安全组,它是自动生成的,允许通过 SSH 端口 22 访问你的实例。这种安全配置负责让你能够通过计算机上的 SSH 客户端访问你的虚拟实例。Mosquitto 代理使用 TCP 端口号1883与发布者和订阅者客户端建立通信。要允许从该 Mosquitto 端口进入访问,你必须编辑当前的入站规则并添加一个端口1883的条目:

一旦你点击了编辑按钮,网站将打开一个弹出窗口以添加新规则和编辑现有规则。点击添加规则按钮以创建一个额外的规则来适应 Mosquitto 代理:

如以下截图所示,将 TCP 端口号输入为1883并完成表单中的其他信息。完成表单后,保存规则并退出窗口:

现在,有了这个配置,端口1883对其他设备是可访问的,并允许与 Mosquitto 代理进行远程通信。您可以使用相同的方法为端口8080添加规则,以允许访问使用web.py开发的 Python 网络应用程序。在未来,您可以添加任何其他端口以允许访问各种服务。尽管在您的虚拟实例上更改安全规则非常容易,但请确保您避免打开过多的端口,以避免任何安全风险。
测试您的云平台
在本测试部分,我们将首先从您的计算机对 Mosquitto 代理进行测试,然后设置 Mosquitto 代理的认证参数。稍后,我们将使用 SSH 文件传输协议将包含 Python 代码的文件和文件夹上传到我们的虚拟实例。
测试 Mosquitto 服务
我们将在我们的物联网平台上检查的第一件事是 Mosquitto 代理的可访问性。在您的计算机上打开终端,并执行以下命令,在替换<Public-IP>时,请使用您的虚拟实例的公共 IP 地址或公共 DNS 地址:
$ mosquitto_pub -h <Public-IP> -t test -m 3
此命令将向指定 IP 地址的 Mosquitto 代理的test主题发布消息值3;在我们的情况下,这是虚拟实例。现在,打开一个单独的终端窗口并执行以下命令来订阅我们的代理上的test主题:
$ mosquitto_sub -h <Public-IP> -t test
执行此命令后,您将能够看到为此主题发布的最新值。使用mosquitto_pub命令发布多条消息,您可以在运行mosquitto_sub命令的另一个终端窗口中看到这些消息的输出。
配置和测试基本安全
如您在上一示例中所见,发布和订阅命令仅使用了 IP 地址来发送和接收数据,而没有使用任何认证参数。这是一个重大的安全漏洞,因为任何互联网用户都可以向您的 Mosquitto 代理发送数据。为了避免未经授权访问您的代理,您必须建立认证凭证。您可以通过按照以下步骤的顺序指定这些参数:
-
如果您尚未通过 SSH 登录到您的实例,请打开一个终端窗口并使用 SSH 登录。登录后,导航到 Mosquitto 目录,并使用以下命令集创建一个名为
passwd的新文件。我们将使用此文件来存储用户名和密码:$ cd /etc/mosquitto $ sudo nano passwd -
在文件中,使用冒号操作符(
:)分隔用户名和密码信息。为了测试目的,我们将使用以下凭证,这些凭证可以在您对 Mosquitto 配置更加熟悉后随时更改:user:password -
按Ctrl + X从 nano 编辑器保存并退出文件。当您被提示确认保存操作时,选择Y并按Enter。
-
在相同的文件夹中,使用 thenano 编辑器打开 Mosquitto 配置文件:
$ sudo nano mosquitto.conf -
在打开的文件中,向下滚动文本内容,直到到达安全部分。在此部分中,找到代码中的
#allow_anonymous true行,并将其替换为allow_anonymous false。确保您已经删除了#符号。通过此操作,我们已经禁用了对 Mosquitto 代理的匿名访问,只有具有适当凭证的客户端才能访问它。 -
在执行了前面的更改之后,在文件中向下滚动,取消注释
#password_file这一行,并将其替换为以下内容:password_file /etc/mosquitto/passwd -
现在您已经为您的代理配置了基本的安全参数,您必须重新启动 Mosquitto 服务以使更改生效。在 Ubuntu 中,Mosquitto 作为后台服务的一部分安装,您可以使用以下命令重新启动它:
$ sudo service mosquitto restart -
要测试这些认证配置,在您的计算机上打开另一个终端窗口,并使用以下命令执行实例的公网 IP 地址。如果您能够成功发布消息且没有任何错误,那么您的 Mosquitto 代理现在已经具有了安全配置:
$ mosquitto_pub -u user -P password -h <Public-Ip> -t test -m 3 -
此外,使用以下命令检查您的 Mosquitto 订阅者:
$ mosquitto_sub -u user -P password -h <Public-Ip> -t test
在实例上上传和测试项目
正如我们在前面的章节中讨论的那样,您始终可以使用您的计算机进行开发。一旦您准备部署,您可以使用这个新配置的虚拟实例作为部署单元。您可以使用名为 PuTTY 的实用程序(docs.aws.amazon.com/AWSEC2/latest/UserGuide/putty.html)或使用 SCP(SSH 复制)命令将您的文件从本地计算机复制到虚拟实例。
现在是时候上传上一章最终编码练习的项目文件了,该练习使用了 Python 和 Mosquitto 库实现了 MQTT 协议。作为提醒,最终练习位于上一章代码仓库中名为Exercise 4 - MQTT gateway的文件夹中。我们将使用 SCP 实用程序将这些文件上传到您的虚拟实例。在我们使用此实用程序之前,让我们首先在您的虚拟实例上创建一个目录。登录到您的虚拟实例,并使用以下命令进入虚拟实例的用户目录:
$ ssh –i <key-name>.pem ubuntu@<public-ip>
$ cd ~
使用字符波浪号(~)与cd命令一起使用,将当前目录更改为主目录,除非您打算在虚拟实例上的其他位置使用。在此位置,使用以下命令创建一个名为project的新空目录:
$ mkdir project
现在,在您正在工作的计算机上(Mac OS X 或 Linux),打开另一个终端窗口,并使用以下命令将整个目录复制到远程实例:
$ scp -v -i test.pem -r <project-folder-path> ubuntu@<your-ec2-static-ip>:~/project
一旦您已成功将文件复制到该位置,您就可以回到登录到您的虚拟实例的终端,并将目录更改为project:
$ cd project
在运行任何命令之前,请确保您已更改 Arduino 草图和 Python 程序中的适当 IP 地址。您需要将之前的 IP 地址替换为您的虚拟实例的 IP 地址。现在,您已经做了这些更改,您可以执行包含 Mosquitto 网关和 Web 应用的 Python 代码来启动程序。从http://<Public-Ip>:8080位置打开您的 Web 浏览器,以查看在自定义物联网平台上运行的 Web 应用。从现在起,您应该能够通过互联网从任何远程位置访问此应用。
小贴士
不要忘记在 Arduino 草图中将 Mosquitto 代理的 IP 地址更改,并再次将草图上传到 Arduino 板。如果没有应用适当的 IP 地址更改,您可能无法获取传感器数据。
摘要
在本章末尾,也就是本书上下文部分的结尾,您应该能够开发自己的物联网项目。在本章中,我们使用了一个商业物联网云平台来处理您的传感器数据。我们还部署了一个云实例来托管开源物联网工具,并创建了自定义物联网云平台的版本。当然,您所学的知识不足以开发可扩展的完整商业产品,但它确实有助于您开始这些产品。在大量情况下,这些材料足以开发 DIY 项目和产品原型,最终将引导您到达最终产品。在接下来的两章中,我们将把所学知识付诸实践,并开发两个完整的物联网硬件项目。我们还将学习一种专门针对基于硬件的物联网产品的项目开发方法,该方法可以应用于将您的原型转换为真实产品。
第十章. 最终项目 – 一个远程家庭监控系统
现在是时候将我们在前几章中学到的每个主题结合起来,创建一个结合 Arduino 编程、Python GUI 开发、MQTT 消息协议和基于 Python 的云应用的项目。正如你可能已经从章节标题中推测出的那样,我们将使用这些组件开发一个远程家庭监控系统。
本章的第一部分涵盖了项目设计过程,包括目标、需求、架构和 UX。一旦我们完成设计过程,我们将进入项目的实际开发,这分为三个独立阶段。接下来,我们将涵盖在处理大型项目时通常会遇到的一些常见故障排除主题。在我们努力开发可用的 DIY 项目时,后面的部分涵盖了扩展项目的技巧和功能。由于与其他书籍中的项目相比,这是一个相当大的项目,我们不会在没有任何策略的情况下直接进入实际开发过程。让我们首先熟悉硬件项目的标准设计方法。
物联网项目的设计方法
开发一个将硬件设备与高级软件服务紧密耦合的复杂产品需要额外的规划层次。对于这个项目,我们将采用适当的产品开发方法,帮助你熟悉创建真实世界硬件项目的流程。然后,可以使用这种方法来规划你自己的项目并将它们提升到下一个层次。以下图表描述了一个典型的原型开发过程,它始终从定义你希望通过产品实现的主要目标开始:

一旦你定义了主要目标集合,你需要将它们分解成项目需求,这些需求包括实现这些目标时原型应执行的每个任务的详细信息。使用项目需求,你需要勾勒出系统的整体架构。下一步包括定义 UX 流程的过程,这将帮助你规划系统的用户交互点。在这个阶段,你将能够识别系统架构、硬件和软件组件中所需的所有更改,以便开始开发。
既然你已经定义了交互点,现在你需要将整个项目开发过程分成多个阶段,并在这些阶段之间分配任务。一旦你完成了这些阶段的发展,你将不得不根据你的架构将这些阶段相互连接,并在需要时调试组件。最后,你必须将你的项目作为一个整体系统进行测试,并解决小问题。在硬件项目中,在复杂开发过程完成后再次处理你的电线路是非常困难的,因为变化可能会对所有其他组件产生重复影响。这个过程将帮助你最小化任何硬件返工和随后的软件修改。
现在你已经了解了方法论,让我们开始实际开发我们的远程家庭监控系统。
项目概述
智能家居是物联网中最定义明确且最受欢迎的子领域之一。任何智能家居最重要的功能是其监控物理环境的能力。幸运的是,我们在前几章中涵盖的练习和项目包括可用于相同目的的组件和功能。在本章中,我们将定义一个将利用这些现有组件和编程练习的项目。在第七章的中期项目中,即“中期项目 – 便携式 DIY 恒温器”,我们创建了一个可部署的恒温器,能够测量温度、湿度和环境光线。如果我们想利用这个中期项目,我们可以在其基础上构建的最近的物联网项目是远程家庭监控系统。该项目将以 Arduino 作为物理环境和基于软件的服务之间的主要交互点。我们将有一个 Python 程序作为中间层,它将连接来自 Arduino 的传感器信息与面向用户的图形界面。让我们首先定义我们想要实现的目标以及满足这些目标的项目需求。
项目目标
Nest 恒温器提供了一个关于一个设计良好的远程监控系统应具备的特性的例子,该系统具有专业功能。实现这一级别的系统能力需要来自大型团队的大量开发工作。尽管很难在我们的项目中包含商业系统支持的每个功能,但我们仍将尝试实现原型项目可以整合的常见功能。
我们计划在这个项目中整合的顶级功能可以通过以下目标来描述。
-
观察物理环境并使其远程可访问
-
向用户提供基本级别的控制以与系统交互
-
展示基本的内置情境意识
项目需求
既然我们已经定义了主要目标,让我们将它们转换为详细的系统需求。项目完成后,系统应能够满足以下要求:
-
它必须能够观察物理现象,如温度、湿度、运动和周围光线。
-
它应提供对传感器信息和执行器(如蜂鸣器、按钮开关和 LED)的本地访问和控制。
-
监控应由使用开源硬件平台 Arduino 开发的单元进行。
-
监控单元应限于收集传感器信息并将其传达给控制单元。
-
控制单元不应包含台式计算机或笔记本电脑。相反,它应该使用像 Raspberry Pi 这样的平台来部署。
-
控制单元应通过利用收集到的传感器信息展示原始级别的态势感知能力。
-
控制单元应具有图形界面,以提供传感器的观察结果和系统的当前状态。
-
系统必须通过基于云的服务通过互联网访问。
-
提供远程访问的 Web 应用程序应具有通过 Web 浏览器显示传感器观察结果的能力。
-
系统还应提供对执行器的基本控制,通过使用 Web 应用程序完成远程访问体验。
-
由于监控单元可能受到计算资源的限制,系统应使用面向硬件的消息协议来传输信息。
尽管还有许多其他可能成为我们项目一部分的次要要求,但它们在这本书中被省略了。如果你对你的远程家庭监控系统有任何额外的计划,这是你必须在你开始设计架构之前定义这些要求的时候。对需求的任何未来更改都可能显著影响开发阶段,并使硬件和软件修改变得困难。在章节的最后部分,我们列出了一些你可能希望考虑实现于未来项目的附加功能。
设计系统架构
从项目目标继续,首先,你需要绘制出系统的概要架构。这个架构草图应包括使系统能够在传感器和远程用户之间传递信息的主要组件。以下图显示了我们的项目架构草图:

根据目标,用户应能够通过互联网访问系统;这意味着我们需要在架构中包含云组件。系统还需要使用资源受限的设备来监控物理环境,这可以使用 Arduino 实现。连接云服务和传感器系统的中间层可以使用 Raspberry Pi 构建。在上一个项目中,我们通过串行连接连接了 Arduino 和 Raspberry Pi,但我们希望摆脱串行连接,开始使用我们家的以太网网络来使系统可部署。因此,基于 Arduino 的单元通过以太网连接到网络,而 Raspberry Pi 则使用 Wi-Fi 连接到同一网络。
为了布局整体系统架构,让我们利用我们设计的草图,如图所示。正如您在下一张图中可以看到的,我们将整体系统转换成了三个主要架构单元:
-
监控站
-
控制中心
-
云服务
在这个图中,我们已经针对我们将要利用的每个主要组件及其相互关联进行了说明。在接下来的章节中,我们将简要定义这三个主要单元。这些单元的详细描述和实现步骤将在本章的单独部分提供。

监控站
我们需要一个资源受限且健壮的单元,该单元将定期与物理环境进行通信。这个监控单元可以使用 Arduino 构建,因为低级微控制器编程可以提供不间断的传感器数据流。在这个阶段使用 Arduino 也将帮助我们避免将基本低级传感器直接与运行在复杂操作系统上的计算机进行接口连接。传感器和执行器通过数字、模拟、PWM 和 I2C 接口连接到 Arduino。
控制中心
控制中心作为传感器信息和用户之间的主要交互点。它还负责将监控站中的传感器信息传递到云服务。控制中心可以使用您的普通计算机或单板计算机(如 Raspberry Pi)开发。我们将使用 Raspberry Pi,因为它可以轻松部署为硬件单元,并且它也足以托管 Python 程序。我们将用一个小型 TFT LCD 屏幕替换 Raspberry Pi 的计算机屏幕来显示 GUI。
云服务
云服务的主要目的是为控制中心提供一个基于互联网的接口,以便用户可以远程访问它。在我们托管一个网络应用程序来执行此操作之前,我们需要一个中间数据中继。这个传感器数据中继充当基于云的网络应用程序和控制中心之间的主机。在这个项目中,我们将使用 Xively 作为平台来收集这些传感器数据。网络应用程序可以托管在互联网服务器上;在我们的情况下,我们将使用我们熟悉的 Amazon AWS。
定义 UX 流程
现在,尽管我们知道整个系统的架构看起来是什么样子,但我们还没有定义用户将如何与之交互。为我们的系统设计用户交互的过程也将帮助我们弄清楚主要组件之间的数据流。
让我们从您家中本地运行的组件开始,即监测站和控制中心。如图所示,我们在控制中心有我们的第一个用户交互点。用户可以观察信息或对其采取行动,如果系统状态是警报。取消警报的用户操作会在控制中心和监测站引发多个操作。我们建议您仔细查看图表,以更好地理解系统的流程。

同样,第二个用户交互点位于网络应用程序中。网络应用程序显示我们在控制中心计算出的观察结果和系统状态,并提供一个界面来取消警报。在这种情况下,取消操作将通过 Xively 传输到控制中心,控制中心的适当操作将与之前的情况相同。然而,在网络应用程序中,用户每次都必须加载网络浏览器来请求数据,而这在控制中心是自动发生的。请查看以下图表以了解网络应用程序的用户体验流程:

所需组件列表
项目所需组件是根据以下三个主要标准推导出来的:
-
易于获取
-
与 Arduino 板兼容
-
由于在此书中之前的使用而熟悉组件
这是您开始项目所需的组件列表。如果您已经完成了之前的练习和项目,您应该已经拥有了大部分组件。如果您不想拆解项目,您可以从 SparkFun、Adafruit 或 Amazon 的网站上获取它们,这些网站的链接将在下一表中提供。
监测站点的硬件组件如下:
| 组件(第一阶段) | 数量 | 链接 |
|---|---|---|
| Arduino Uno | 1 | www.sparkfun.com/products/11021 |
| Arduino 以太网盾 | 1 | www.sparkfun.com/products/9026 |
| 面包板 | 1 | www.sparkfun.com/products/9567 |
| TMP102 温度传感器 | 1 | www.sparkfun.com/products/11931 |
| HIH-4030 湿度传感器 | 1 | www.sparkfun.com/products/9569 |
| 小型光敏电阻 | 1 | www.sparkfun.com/products/9088 |
| PIR 运动传感器 | 1 | www.sparkfun.com/products/8630 |
| 超亮 RGB LED,共阳极 | 1 | www.adafruit.com/product/314 |
| 蜂鸣器 | 1 | www.adafruit.com/products/160 |
| 按钮开关 | 1 | www.sparkfun.com/products/97 |
| Arduino 开发阶段用 USB 线 | 1 | www.sparkfun.com/products/512 |
| Arduino 部署阶段电源 | 1 | www.amazon.com/Arduino-9V-1A-Power-Adapter/dp/B00CP1QLSC/ |
| 电阻 | 如需 | 220 欧姆、1 千欧姆和 10 千欧姆 |
| 连接线 | 如需 | 无 |
控制中心的硬件组件如下:
| 组件(第一阶段) | 数量 | 链接 |
|---|---|---|
| 树莓派 | 1 | www.sparkfun.com/products/11546 |
| TFT LCD 屏幕 | 1 | www.amazon.com/gp/product/B00GASHVDU/ |
| SD 卡(8 GB) | 1 | www.sparkfun.com/products/12998 |
| Wi-Fi 拓展卡 | 1 | www.amazon.com/Edimax-EW-7811Un-150Mbps-Raspberry-Supports/dp/B003MTTJOY |
| 树莓派电源 | 1 | www.amazon.com/CanaKit-Raspberry-Supply-Adapter-Charger/dp/B00GF9T3I0 |
| 键盘、鼠标、USB 集线器和显示器 | 如需 | 开发和调试阶段所需 |
定义项目开发阶段
根据系统架构,我们拥有三个主要单元,它们协同创建远程家庭监控系统项目。整体硬件和软件开发流程也与这三个单元相一致,可以如下分配:
-
监控站开发阶段
-
控制中心开发阶段
-
网络应用程序开发阶段
监控站阶段的软件开发包括编写 Arduino 代码以监控传感器并执行执行器动作,同时将此信息发布到控制中心。开发阶段的中间层,即基于 Raspberry Pi 的控制中心,托管了 Mosquitto 代理。此阶段还包括包含 GUI、态势感知逻辑和与 Xively 云服务通信的子例程的 Python 程序。最后阶段,云服务,包括两个不同的组件,传感器数据中继和 Web 应用程序。我们将使用 Xively 平台作为我们的传感器数据中继,并将使用 Python 在 Amazon AWS 云实例上开发 Web 应用程序。现在,让我们进入实际的开发过程,我们的第一个目的地将是基于 Arduino 的监控站。
第 1 阶段 - 使用 Arduino 的监控站
正如我们所讨论的,监控系统的主要任务是接口传感器组件并将这些传感器生成的信息传达给观察者。你将使用 Arduino Uno 作为中央微控制器组件来集成这些传感器和执行器。我们还需要 Arduino Uno 和控制中心之间的通信手段,我们将利用 Arduino 以太网盾来实现这一目的。让我们讨论监控站的硬件架构及其组件。
设计监控站
我们已经在第八章“Arduino 网络介绍”和第九章“Arduino 与物联网”的各种练习中设计了基于 Arduino 和以太网盾的单元。因此,我们假设你已经熟悉将以太网盾与 Arduino 板进行接口。我们将使用 Arduino 板连接各种传感器和执行器,如下面的图所示。如图所示,传感器将向 Arduino 板提供数据,而执行器将从 Arduino 板获取数据。尽管我们自动收集这些传感器的环境数据,但按钮的数据将通过手动用户输入进行收集。

查看以下 Fritzing 图以了解监控站中的详细连接。正如你在我们的硬件设计中看到的那样,温度传感器 TMP102 通过 I2C 接口连接,这意味着我们需要 SDA 和 SCL 线。我们将使用 Arduino 板的模拟引脚 5 和 6 来分别接口 SDA 和 SCL。湿度(HIH-4030)和环境光传感器也提供模拟输出,并连接到 Arduino 板的模拟引脚。同时,蜂鸣器、按钮开关和 PIR 运动传感器通过数字 I/O 引脚连接。超级流明 RGB LED 是正极 LED;这意味着它总是使用正极引脚供电,而 R、G 和 B 引脚通过 PWM 引脚控制。
确保你将所有组件正确连接到以下图中指定的引脚:

注意
你可以从learn.adafruit.com/all-about-leds教程中了解更多关于 RGB LED 与 Arduino 接口的信息。
如果你使用的是除 Arduino Uno 以外的 Arduino 板,你将不得不在 Arduino 代码中调整适当的引脚编号。此外,确保这个 Arduino 板与以太网盾兼容。
在电路连接方面,你可以使用前面图中所示的面包板,或者如果你感到舒适,你可以使用 PCB 原型板并焊接组件。在我们的设置中,我们首先在面包板上测试了组件,一旦测试通过,我们就焊接了组件,如图所示。如果你尝试焊接 PCB 板,请确保你有完成这项工作的必要组件。与面包板相比,PCB 原型板将提供更坚固的性能,但这也将使你在之后调试和更换组件变得困难。

如果你已经准备好了电路连接,请使用 USB 线将 Arduino 连接到你的电脑。同时,使用以太网线将以太网盾连接到你的家庭路由器。
监控站的 Arduino 草图
在进入编码阶段之前,请确保你已经收集了项目的预构建 Arduino 代码。你可以在本章的代码文件夹中找到它,文件名为Arduino_monitoring_station.ino。该代码实现了支持监控站整体 UX 流程所需的基本逻辑,这是我们之前章节中讨论过的。在接下来的章节中,我们将逐一介绍程序的主要部分,以便你更好地理解这些代码片段。现在,在 Arduino IDE 中打开这个草图。你已经熟悉了为 Arduino 设置 IP 地址。在上一个章节中,你也学习了如何使用 Arduino MQTT 库PubSubClient,这意味着你的 Arduino IDE 上应该已经安装了PubSubClient库。在代码的开头,我们还声明了一些常量,例如 MQTT 服务器的 IP 地址和 Arduino 的 IP 地址,以及各种传感器和执行器的引脚号。
注意
你需要根据你的网络设置更改监控站和控制中心的 IP 地址。确保在上传 Arduino 代码之前执行这些修改。
在代码结构中,我们有两个强制性的 Arduino 函数,setup()和loop()。在setup()函数中,我们将设置 Arduino 引脚类型和 MQTT 订阅通道。在同一个函数中,我们还将设置一个用于publishData()函数的计时器,并附加一个按钮按下时的中断。
发布传感器信息
publishData()函数读取传感器输入,并将这些数据发布到位于控制中心的 Mosquitto 代理。正如你在下面的代码片段中可以看到的,我们正在逐个测量传感器值,并使用client.publish()方法将它们发布到代理:
void publishData (){
Wire.requestFrom(partAddress,2);
byte MSB = Wire.read();
byte LSB = Wire.read();
int TemperatureData = ((MSB << 8) | LSB) >> 4;
float celsius = TemperatureData*0.0625;
temperatureC = dtostrf(celsius, 5, 2, message_buff2);
client.publish("MonitoringStation/temperature", temperatureC);
float humidity = getHumidity(celsius);
humidityC = dtostrf(humidity, 5, 2, message_buff2);
client.publish("MonitoringStation/humidity", humidityC);
int motion = digitalRead(MotionPin);
motionC = dtostrf(motion, 5, 2, message_buff2);
client.publish("MonitoringStation/motion", motionC);
int light = analogRead(LightPin);
lightC = dtostrf(light, 5, 2, message_buff2);
client.publish("MonitoringStation/light", lightC);
}
如果你查看setup()函数,你会注意到我们使用了一个名为SimpleTimer的库来为这个函数设置一个timer方法。该方法定期执行publishData()函数,而不会中断和阻塞 Arduino 执行周期的实际流程。在下面的代码片段中,数字300000代表毫秒级的延迟时间,即 5 分钟:
timer.setInterval(300000, publishData);
注意
你需要下载并导入SimpleTimer库才能成功编译和运行代码。你可以从github.com/infomaniac50/SimpleTimer下载该库。
订阅执行器动作
你可以在setup()函数中看到,我们通过订阅MonitoringStation/led和MonitoringStation/buzzer通道来初始化代码。client.subscribe()方法将确保每当 Mosquitto 代理收到这些通道的任何更新时,基于 Arduino 的监控系统都会得到通知:
if (client.connect("MonitoringStation")) {
client.subscribe("MonitoringStation/led");
client.subscribe("MonitoringStation/buzzer");
}
编程一个中断来处理按钮的按下
我们已经处理了监控站的发布和订阅功能。现在,我们需要集成由用户输入控制的按钮开关。在 Arduino 编程例程中,我们运行一个周期性循环来检查引脚的状态。然而,如果按钮被按下,这可能没有用,因为它需要立即采取行动。按下按钮的动作是通过 Arduino 中断来处理的,如下面的代码行所示:
attachInterrupt(0, buttonPress, RISING);
上一行代码将引脚 0(数字引脚 2)的中断与buttonPress()函数关联。这个函数会在中断状态改变时触发蜂鸣器。换句话说,当用户按下按钮时,无论蜂鸣器的当前状态如何,蜂鸣器都会立即关闭:
void buttonPress(){
digitalWrite(BUZZER, LOW);
Serial.println("Set buzzer off");
}
测试
当前的 Arduino 代码用于与控制中心通信以发布和订阅数据,但我们还没有设置 Mosquitto 代理来处理这些请求。您仍然可以使用 USB 线将 Arduino 草图上传到您的监控站。这将不会导致监控站有任何有益的行动,您只能使用Serial.prinln()命令来打印各种传感器测量值。因此,我们将开发控制中心,这样我们就可以开始处理来自监控站的通信请求。
第二阶段 - 使用 Python 和树莓派的控制中心
为了将系统状态和其他传感器观察结果传达给用户,控制中心需要执行各种操作,包括从监控站获取原始传感器数据,计算系统状态,将此数据报告给云服务,并使用 GUI 显示观察结果。虽然控制中心包括两个主要硬件组件(树莓派和 TFT 液晶显示屏),但它还包括两个主要软件组件(Mosquitto 代理和 Python 代码)来处理控制中心逻辑。
小贴士
我们使用树莓派而不是普通计算机,因为我们希望控制中心成为一个可部署和便携的单位,可以安装在墙上。
您仍然可以使用自己的计算机来编辑和测试用于开发目的的 Python 代码,而不是直接使用树莓派。然而,一旦您准备部署,我们建议您切换回树莓派。
控制中心架构
树莓派是控制中心的主要计算单元,作为整个系统的“大脑”。由于树莓派被用作普通计算机的替代品,控制中心的架构可以互换地使用计算机代替树莓派。正如您在下图中可以看到的,控制中心通过 Wi-Fi 连接到家庭网络,这将使其对监控站可访问。控制中心包括 Mosquitto 代理;这是监控站和用于控制中心的 Python 程序之间的通信点。Python 程序利用 Tkinter 库进行 GUI,并使用 paho_mqtt 库与 Mosquitto 代理通信。通过利用这两个库,我们可以将监控站的传感器信息传递给用户。然而,我们需要一个单独的安排来建立控制中心和云服务之间的通信。在我们的整体系统架构中,控制中心被设计为与中间数据中继 Xively 通信。Python 代码使用 xively-python 库来实现这种通信。

在 第八章,Arduino 网络入门中,我们已向您提供了安装 Mosquitto 代理、Python-mosquitto 库和 xively-python 库的方法。我们还在 第七章,中期项目 – 便携式 DIY 温度控制器中学习了使用树莓派设置 TFT LCD 屏幕的过程。如果您尚未完成这些练习,请参阅那些教程。假设您已配置了 Mosquitto 代理和所需的 Python 库,您可以继续到下一节,该节包括实际的 Python 编程。
控制中心的 Python 代码
在您开始在 Python 代码中接口这些库之前,首先使用以下简单命令从命令行启动您的 Mosquitto 代理:
$ mosquitto
确保每次启动或重启 Mosquitto 代理时都重新启动您的监控站。这个操作将确保您的监控站连接到 Mosquitto 代理,因为在我们 Arduino 代码中,建立连接的过程只会在设置过程的开始执行一次。
当前项目的 Python 代码位于本章代码文件夹中,文件名为 controlCenter.py。使用您的 Python IDE 打开此文件,在执行之前修改适当的参数值。这些参数包括 Mosquitto 代理的 IP 地址以及 Xively 虚拟设备的 feed ID 和 API 密钥。您应该已经从上一章中获得了 Xively 虚拟设备的 feed ID 和 API 密钥:
cli.connect("10.0.0.18", 1883, 15)
FEED_ID = "<feed-id>"
API_KEY = "<api-key"
如果你正在使用 Mosquitto 代理的本地实例,你可以将 IP 地址替换为 127.0.0.1。否则,将 10.0.0.18 地址替换为托管 Mosquitto 代理的计算机的适当 IP 地址。现在让我们尝试理解代码。
注意
有时在 Mac OS X 上,由于一个未知的错误,你无法并行运行 Tkinter 窗口和 Python 线程。你应该能够在 Windows 和 Linux 环境中成功执行程序。这个程序已经在 Raspberry Pi 上进行了测试,这意味着在部署控制中心时,你不会遇到相同的错误。
使用 Tkinter 创建 GUI
在之前的练习中,我们总是使用单个 Python 线程来运行程序。这种做法不会帮助我们并行执行多个任务,例如从监控站获取传感器观察结果,并同时更新 GUI 以显示该信息。作为解决方案,我们在本次练习中引入了多线程。由于我们需要两个独立的循环,一个用于 Tkinter,另一个用于 paho-mqtt,我们将它们独立地在不同的线程中运行。主线程将运行与 Mosquitto 和云服务相关的函数,而第二个线程将处理 Tkinter GUI。在下面的代码片段中,你可以看到我们使用 threading.thread 参数初始化了 controlCenterWindow() 类。因此,当我们主程序中执行 window = controlCenterWindow() 时,它将为这个类创建另一个线程。基本上,这个类在填充标签和其他 GUI 组件的同时创建 GUI 窗口。当新的传感器观察结果到达时,标签需要更新,它们被声明为类变量,并且可以从类实例中访问。正如你在下面的代码片段中所看到的,我们已将温度、湿度、光和运动的标签声明为类变量:
class controlCenterWindow(threading.Thread):
def __init__(self):
# Tkinter canvas
threading.Thread.__init__(self)
self.start()
def callback(self):
self.top.quit()
def run(self):
self.top = Tkinter.Tk()
self.top.protocol("WM_DELETE_WINDOW", self.callback)
self.top.title("Control Center")
self.statusValue = Tkinter.StringVar()
self.statusValue.set("Normal")
self.tempValue = Tkinter.StringVar()
self.tempValue.set('-')
self.humdValue = Tkinter.StringVar()
self.humdValue.set('-')
self.lightValue = Tkinter.StringVar()
self.lightValue.set('-')
self.motionValue = Tkinter.StringVar()
self.motionValue.set('No')
# Begin code subsection
# Declares Tkinter components
# Included in the code sample of the chapter
# End code subsection
self.top.mainloop()
之前的代码片段中没有包含我们声明 Tkinter 组件的部分,因为它与我们中期项目中编写的代码类似。如果你对 Tkinter 相关问题有疑问,请参阅第六章,存储和绘制 Arduino 数据,以及第七章,中期项目 – 一款便携式 DIY 温度控制器。
与 Mosquitto 代理通信
在控制中心级别,我们订阅了来自监测站发布的主题,即MonitoringStation/temperature、MonitoringStation/humidity等。如果你对 Arduino 代码进行了任何修改以更改 MQTT 主题,你需要在此部分反映这些更改。如果监测站发布的话题与控制中心代码中的话题不匹配,你将不会收到任何更新。正如你在 Python 代码中所看到的,我们将on_message和on_publish方法与非常重要的函数关联。每当有消息从订阅者那里到达时,客户端将调用与on_message方法关联的函数。然而,每当 Python 代码发布消息时,onPublish()函数将被调用:
cli = mq.Client('ControlCenter')
cli.on_message = onMessage
cli.on_publish = onPublish
cli.connect("10.0.0.18", 1883, 15)
cli.subscribe("MonitoringStation/temperature", 0)
cli.subscribe("MonitoringStation/humidity", 0)
cli.subscribe("MonitoringStation/motion", 0)
cli.subscribe("MonitoringStation/light", 0)
cli.subscribe("MonitoringStation/buzzer", 0)
cli.subscribe("MonitoringStation/led", 0)
计算系统的状态和情况感知
控制中心被分配了计算整个系统状态的任务。控制中心使用当前温度和湿度的值来计算系统的状态,状态可以是Alert(警报)、Caution(注意)或Normal(正常)。为了计算状态,每当控制中心从监测站接收到温度或湿度的更新时,它都会执行calculateStatus()函数。根据当前的情况感知逻辑,如果测量的温度高于 45 摄氏度或低于 5 摄氏度,我们将系统的状态称为Alert。同样,你可以从以下代码片段中识别出温度和湿度值的范围,以确定Caution和Normal状态:
def calculateStatus():
if (tempG > 45):
if (humdG > 80):
status = "High Temperature, High Humidity"
elif (humdG < 20):
status = "High Temperature, Low Humidity"
else:
status = "High Temperature"
setAlert(status)
elif (tempG < 5):
if (humdG > 80):
status = "Low Temperature, High Humidity"
elif (humdG < 20):
status = "Low Temperature, Low Humidity"
else:
status = "Low Temperature"
setAlert(status)
else:
if (humdG > 80):
status = "High Humidity"
setCaution(status)
elif (humdG < 20):
status = "Low Humidity"
setCaution(status)
else:
status = "Normal"
setNormal(status)
与 Xively 通信
控制中心在从订阅的话题接收到消息时也需要与 Xively 通信。我们已经熟悉了在 Xively 上设置虚拟设备和数据流的过程。打开你的 Xively 账户,创建一个名为ControlCenter的虚拟设备。记下该设备的 feed ID 和 API 密钥,并在当前代码中替换它们。一旦你有了这些值,在这个虚拟设备中创建Temperature、Humidity、Light、Motion、Buzzer和Status通道。
观察 Python 代码,你可以看到我们为每个主题声明了单独的数据流,并将它们与适当的 Xively 通道关联。以下代码片段显示了仅针对温度观测的数据流,但代码还包含了对所有其他传感器观测的类似配置:
try:
datastreamTemp = feed.datastreams.get("Temperature")
except HTTPError as e:
print "HTTPError({0}): {1}".format(e.errno, e.strerror)
datastreamTemp = feed.datastreams.create("Temperature", tags="C")
print "Creating new channel 'Temperature'"
一旦控制中心从监测站接收到消息,它将使用最新的值更新数据流,并将这些更改推送到 Xively。同时,我们还将使用onMessage()函数更新Tkinter GUI 中的适当标签。我们将使用相同的代码片段为所有订阅的通道:
if msg.topic == "MonitoringStation/temperature":
tempG = float(msg.payload)
window.tempValue.set(tempG)
datastreamTemp.current_value = tempG
try:
datastreamTemp.update()
except HTTPError as e:
print "HTTPError({0}): {1}".format(e.errno, e.strerror)
控制中心还实现了跨系统设置系统状态的功能,一旦使用 calculateStatus() 函数计算后。有三个不同的函数通过一种类似于我们在前一个代码片段中描述的方法来执行此任务。这些函数包括 setAlert()、setCaution() 和 setNormal(),它们分别与 Alert、Caution 和 Normal 相关联。在更新系统状态时,这些函数还会通过将 LED 和蜂鸣器值发布到 Mosquitto 代理来执行蜂鸣器和 LED 动作:
def setAlert(status):
window.statusValue.set(status)
datastreamStatus.current_value = "Alert"
try:
datastreamStatus.update()
except HTTPError as e:
print "HTTPError({0}): {1}".format(e.errno, e.strerror)
cli.publish("MonitoringStation/led", 'red')
cli.publish("MonitoringStation/buzzer", 'ON')
检查和更新蜂鸣器的状态
在控制中心,如果系统状态被确定为 Alert,我们将蜂鸣器的状态设置为 ON。如果你回顾 UX 流程,你会注意到我们还想包括一个让用户手动关闭蜂鸣器的功能。checkBuzzerFromXively() 函数跟踪来自 Xively 的蜂鸣器状态,如果用户使用 Web 应用程序手动关闭蜂鸣器,这个函数就会触发蜂鸣器。
为了从 GUI 和情况感知线程独立地继续此过程,我们需要为这个函数创建另一个线程。这个线程上的定时器将自动每 30 秒执行一次函数:
def checkBuzzerFromXively():
try:
datastreamBuzzer = feed.datastreams.get("Buzzer")
buzzerValue = datastreamBuzzer.current_value
buzzerValue = str(buzzerValue)
cli.publish("MonitoringStation/buzzer", buzzerValue)
except HTTPError as e:
print "HTTPError({0}): {1}".format(e.errno, e.strerror)
print "Requested channel doesn't exist"
threading.Timer(30, checkBuzzerFromXively).start()
通过在单独的线程上每 30 秒运行此函数,控制中心将检查 Xively 通道的状态,如果状态设置为 OFF,则停止蜂鸣器。我们将在下一节中解释用户如何更新蜂鸣器的 Xively 通道。
使用监控站测试控制中心
假设你的 Mosquitto 代理正在运行,使用更改后的参数执行 controlCenter.py 代码。然后,启动监控站。过一会儿,你将在终端上看到控制中心已经开始从监控站上初始化的发布者那里接收消息。控制中心发布者的更新间隔取决于监控站配置的发布间隔。
注意
Arduino 代码在开机后只执行一次连接到 Mosquitto 代理的过程。如果你在那之后启动 Mosquitto 代理,它将无法与代理通信。因此,你需要确保在启动监控站之前启动 Mosquitto 代理。
如果需要因任何原因重新启动 Mosquitto 代理,首先移除并重新启动监控站。

在程序执行时,你将能够看到一个小的 GUI 窗口,如下面的截图所示。此窗口显示传感器的温度、湿度、环境光和运动值。除了这些值,GUI 还显示了系统的状态,在这个截图中是正常。你也可以观察到,每次控制中心从监控站获取更新时,系统的状态和传感器观察结果都会实时改变:

如果这个设置在你的计算机上运行正确,让我们继续在树莓派上部署控制中心。
在树莓派上设置控制中心
安装 Raspbian 操作系统的过程在第七章《中期项目 – 一个便携式 DIY 恒温器》中解释。你可以使用中期项目中使用的相同模块,或者设置一个新的模块。一旦安装了 Raspbian 并配置了 TFT 屏幕,通过 USB 端口连接 Wi-Fi 适配器。在这个阶段,我们假设你的树莓派已经连接到显示器、键盘和鼠标以执行基本更改。虽然我们不推荐这样做,但如果你对 TFT 屏幕操作感到舒适,你也可以用它来进行以下操作:
-
启动你的树莓派并登录。在命令提示符下,执行以下命令以进入图形桌面模式:
$ startx -
一旦你的图形桌面启动,你将能够看到WiFi 配置工具的图标。双击此图标并打开WiFi 配置工具。扫描无线网络并连接到有监控站的 Wi-Fi 网络。当被要求时,在名为PSK的表单窗口中输入你的网络密码,并连接到你的网络。
-
现在,你的树莓派已经通过它连接到本地家庭网络和互联网。是时候更新现有包并安装所需的包了。要更新树莓派的现有系统,请在终端中执行以下命令:
$ sudo apt-get update $ sudo apt-get upgrade -
一旦你的系统更新到最新版本,就是时候在你的树莓派上安装 Mosquitto 代理了。Raspbian 操作系统默认仓库中有 Mosquitto,但没有我们需要的当前版本。要安装 Mosquitto 的最新版本,请在终端中执行以下命令:
$ curl -O http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key $ sudo apt-key add mosquitto-repo.gpg.key $ rm mosquitto-repo.gpg.key $ cd /etc/apt/sources.list.d/ $ sudo curl -O http://repo.mosquitto.org/debian/mosquitto-repo.list $ sudo apt-get update $ sudo apt-get install mosquitto, mosquitto-clients -
要安装其他 Python 依赖项,我们首先使用
apt-get安装 Setuptools 包:$ sudo apt-get install python-setuptools -
使用 Setuptools,我们现在可以安装所有必需的 Python 库,如
paho_mqtt、xively-python和web.py:$ sudo easy_install pip $ sudo pip install xively-python web.py paho_mqtt
现在我们已经安装了所有必要的软件工具,可以在树莓派上运行我们的控制中心,是时候配置树莓派,使其能够为远程家庭监控系统等关键系统提供不间断的运行:
-
在当前配置的树莓派中,树莓派的屏幕会在一段时间后进入休眠状态,此时 Wi-Fi 连接也会被终止。为了避免这个问题并强制屏幕保持活跃,你需要执行以下更改。使用以下命令打开
lightdm.conf文件:$ sudo nano /etc/lightdm/lightdm.conf -
在文件中,导航到
SetDefaults部分并编辑以下行:xserver-command-X –s 0 dpms -
现在您的树莓派已经设置好了,是时候将程序文件从您的电脑复制到树莓派上了。您可以使用 SCP、PuTTY 或只是一个 USB 驱动器来将必要的文件传输到树莓派。
如果你按照指定的方式安装和配置了所有内容,你的程序应该会无错误运行。你可以使用以下命令在后台持续运行 Python 程序:
$ nohup python controlCenter.py &
我们在树莓派上最后要设置的是 TFT LCD 屏幕。TFT LCD 屏幕的安装和配置过程在第七章《中期项目 – 一个便携式 DIY 恒温器》中有描述。请按照给定的顺序设置屏幕。现在,控制中心模块、树莓派和 TFT 屏幕可以部署在你家的任何地方。
第三阶段 – 使用 Xively、Python 和 Amazon 云服务的网络应用
整个系统的云服务模块使您可以通过互联网远程访问您的监控站。该单元通过作为控制中心扩展版本的 Web 应用与用户交互。使用这个 Web 应用,用户可以在远程控制关闭蜂鸣器的同时,观察来自监控站的传感器信息和由控制中心计算的系统状态。那么,云服务的架构是什么样的呢?
云服务架构
下面的图中显示了云服务模块及其相关组件的架构。在云服务架构中,我们使用 Xively 作为网络应用和控制中心之间的中间数据中继。控制中心将来自监控站的观测数据推送到 Xively 通道。Xively 存储并转发数据到托管在 Amazon AWS 上的网络应用。Amazon AWS 上的服务器实例用于使网络应用通过互联网可访问。服务器实例运行 Ubuntu 操作系统和用 Python 的web.py库开发的网络应用。

在之前的阶段,我们已经介绍了设置 Xively 和通道以容纳传感器数据的过程。在控制中心代码中,我们也解释了如何将更新的观测数据推送到适当的 Xively 通道。因此,在这个阶段,我们实际上没有关于 Xively 平台的内容要介绍,我们可以继续到 Web 应用程序。
部署在 Amazon AWS 上的 Python Web 应用程序
在上一章中,我们设置了一个 Amazon AWS 云实例来托管 Web 应用程序。你也可以使用相同的实例来托管远程家庭监控系统中的 Web 应用程序。然而,请确保你在服务器上安装了web.py库。
-
在你的电脑上,打开
Web_Application文件夹,然后在你的编辑器中打开RemoteMonitoringApplication.py文件。 -
在代码中,你可以看到我们只是扩展了我们在第九章中创建的 Web 应用程序程序,即Arduino 和物联网。我们使用基于
web.py的模板以及GET()和POST()函数来启用 Web 应用程序。 -
在应用程序中,我们从每个 Xively 通道获取信息,并通过一个单独的函数进行处理。例如,
fetchTempXively()函数从 Xively 获取温度信息。每次执行POST()函数时,fetchTempXively()函数都会从 Xively 获取最新的温度读数。这也意味着 Web 应用程序不会自动填充和刷新最新信息,而是等待POST()执行适当的函数:def fetchTempXively(): try: datastreamTemp = feed.datastreams.get("Temperature") except HTTPError as e: print "HTTPError({0}): {1}".format(e.errno, e.strerror) print "Requested channel doesn't exist" return datastreamTemp.current_value -
Web 应用程序还提供了从用户界面控制蜂鸣器的功能。以下代码片段添加了Buzzer Off按钮和其他
Form组件。在此按钮按下后提交表单,Web 应用程序将执行setBuzzer()函数:inputData = web.input() if inputData.btn == "buzzerOff": setBuzzer("OFF") -
setBuzzer()函数访问 Xively 通道Buzzer,如果按下Buzzer Off按钮,则发送关闭值。当前的 Web 应用程序不包括Buzzer On按钮,但你可以通过重用我们为Buzzer Off按钮开发的代码轻松实现此功能。此函数为其他控制点提供了参考代码,你可以稍作修改后重用:def setBuzzer(statusTemp): try: datastream = feed.datastreams.get("Buzzer") except HTTPError as e: print "HTTPError({0}): {1}".format(e.errno, e.strerror) datastream = feed.datastreams.create("Buzzer", tags="buzzer") print "Creating new Channel 'Buzzer" datastream.current_value = statusTemp try: datastream.update() except HTTPError as e: print "HTTPError({0}): {1}".format(e.errno, e.strerror) -
在代码中,你还需要修改 Xively 的 feed ID 和 API 密钥,并将它们替换为你从虚拟设备获得的值。完成此修改后,运行以下命令。如果一切按计划进行,你将能够在你的网络浏览器中打开 Web 应用程序。
$ python RemoteMonitoringApplication.py
如果你正在电脑上运行 Python 代码,你可以打开http://127.0.0.1:8080来访问应用程序。如果你在云服务器上运行应用程序,你需要输入你的服务器 IP 地址或域名来访问 Web 应用程序,http://<AWS-IP-address>:8080。如果 Web 应用程序在云上运行,它可以通过互联网从任何地方访问,这是原始项目要求之一。通过这一最后一步,你已经成功完成了基于 Arduino 和 Python 的远程家庭监控系统开发。
测试 Web 应用程序
当你在浏览器中打开 Web 应用程序时,你将能够看到以下截图所示的类似输出。正如你所见,Web 应用程序显示了温度、湿度、光照和运动值。刷新按钮从 Xively 再次获取传感器数据并重新加载应用程序。蜂鸣器关闭按钮将 Xively 的蜂鸣器通道值设置为关闭,然后由控制中心接收到,随后在监测站关闭蜂鸣器:

测试和故障排除
由于涉及到的组件数量和与之相关的复杂编程,整个项目是一个复杂的系统,需要进行测试和调试。在你开始故障排除之前,确保你已经按照前面章节中描述的步骤正确操作。以下是在项目执行过程中可能出现的几个问题的解决方案:
-
故障排除单个传感器的性能:
-
如果你的传感器测量值远远偏离预期值,你首先想要评估的是传感器引脚与 Arduino 板的连接。确保你已经正确连接了数字、模拟和 PWM 引脚。
-
检查你的以太网盾板是否正确连接到 Arduino Uno。
-
评估每个组件的 5V 电源和地线的连接。
-
-
避免 Xively 的更新限制
-
Xively 对你在有限时间内可以执行的最大交易数量有限制。在运行控制中心代码时,如果你遇到超出限制的错误,请在你的访问限制解除之前等待 5 分钟。
-
在控制中心级别增加连续 Xively 更新的延迟:
threading.Timer(120, checkBuzzerFromXively).start() -
减少监测站发布的消息频率:
timer.setInterval(600000, publishData); -
你还可以通过将数据格式化为 JSON 或 XML 来组合各种 Xively 通道。
-
-
与 Arduino 的最大电流消耗限制一起工作:
-
Arduino 的+5V 电源引脚和数字引脚分别可以提供最大 200 mA 和 40 mA 的电流。当直接从 Arduino 板运行传感器时,确保你不超过这些限制。
-
确保所有传感器的总电流需求小于 200 mA。否则,组件将无法获得足够的电力来运行,这将导致传感器信息错误。
-
您可以为需要大量电流的组件提供外部电源,并通过 Arduino 本身控制此电源机制。您需要一个作为开关工作的晶体管,然后可以使用 Arduino 的数字引脚来控制它。
learn.adafruit.com/adafruit-arduino-lesson-13-dc-motors/transistors教程展示了类似用于直流电机的示例。
-
-
解决网络问题:
-
在某些情况下,由于网络问题,您的监控站可能无法与控制中心通信。
-
通过为 Arduino 和 Raspberry Pi 都使用手动 IP 地址,可以解决这个问题。在我们的项目中,我们为 Arduino 使用手动 IP 地址,但 Raspberry Pi 是通过 Wi-Fi 网络连接的。在大多数情况下,当您使用家庭 Wi-Fi 网络时,Wi-Fi 路由器被设置为在设备每次重新连接到路由器时提供动态 IP 地址。
-
您可以通过将 Wi-Fi 路由器配置为为 Raspberry Pi 提供固定 IP 地址来解决这个问题。由于每个场景中的 Wi-Fi 路由器类型和型号都不同,您将需要使用其用户手册或在线帮助论坛来设置它。
-
-
处理蜂鸣器相关的问题:
-
有时蜂鸣器的声音可能太大或太小,这取决于您使用的传感器。您可以使用 PWM 来配置蜂鸣器的强度。在我们的项目中,我们使用 Arduino 数字引脚 9 连接蜂鸣器。此引脚也支持 PWM。在您的 Arduino 代码中,修改行以反映 PWM 引脚的变化。将
digitalWrite(BUZZER, HIGH);行替换为analogWrite(BUZZER, 127);。 -
此程序将蜂鸣器的强度从原始水平减半。您还可以将 PWM 值从 0 更改为 255,并将蜂鸣器声音的强度从最低设置为最高。
-
-
控制中心 GUI 校准:
-
根据您使用的 TFT 液晶屏幕的大小,您需要调整
Tkinter的主窗口大小。 -
首先,在您的 Raspberry Pi 上运行当前代码,如果您看到 GUI 窗口与屏幕不匹配,请在初始化主窗口后添加以下代码行:
top.minsize(320,200) -
此代码将修复 2.8 英寸 TFT 液晶屏幕尺寸的问题。在之前的代码片段中,
320和200分别代表宽度和长度的像素大小。对于其他屏幕尺寸,相应地更改像素大小。
-
-
测试 LED:
-
在当前的代码配置中,只有当系统切换到
Alert或Caution时,LED 才会打开。这意味着除非这些情况发生,否则您无法测试 LED。要检查它们是否正常工作,请在控制中心执行以下命令:$ mosquitto_pub –t "MonitoringStation/led" –m "red" -
这个命令将使 LED 变红。要关闭 LED,只需在之前的代码中将
red替换为off。 -
如果没有任何灯光亮起,你应该检查 LED 的连接线。此外,检查网络相关的问题,因为 Mosquitto 本身可能不工作。
-
如果你看到除了红色以外的任何颜色,这意味着你没有正确连接 LED,你需要交换你的 LED 引脚配置。如果你使用的是非超级流光 RGB 的 LED,你应该检查数据表中的引脚布局并重新组织连接。
-
扩展你的远程家庭监控系统
要从 DIY 项目原型成功创建商业产品,你需要在基本功能之上添加额外的功能层。这些功能实际上在使用系统时为用户提供了便利。另一个可区分的特征是系统的可触摸性,这使得大规模生产和支持成为可能。尽管你可以实现很多功能,但我们建议以下主要改进来提升当前项目的水平。
利用多个监测站
在这个项目中,我们开发了一个作为原型的监测站,它具有一系列由远程家庭监控系统展示的功能。远程监控系统可以拥有多个监测站来覆盖各种地理区域,例如房屋内的不同房间,或不同的办公室隔间。基本上,大量的监测站可以覆盖更广泛的区域,并提供对你试图监控区域的效率监控。如果你想通过一系列监测站扩展当前项目,你将需要以下一些修改:
-
每个监测站可以有自己的控制中心,或者根据应用需求为所有监测站集中一个控制中心。
-
你将不得不更新控制中心的 Python 代码以适应这些变化。这些变化的例子包括修改 MQTT 的主题标题、在这些监测站之间协调、更新 Xively 数据模型以进行更新等。
-
免费 Xively 账户可能无法处理来自监测站的大量数据。在这种情况下,你可以优化更新速率和/或有效载荷大小,或者升级你的 Xively 账户以满足要求。你也可以求助于其他免费服务,如 ThingSpeak、Dweet.io 和 Carriots,但你将需要对现有的代码结构进行重大修改。
-
你还可以更新网络应用程序,为你提供监测站的选项菜单或一次性显示所有监测站。你还需要更改代码以生成修改后的数据模型。
扩展感官能力
在传感器方面,我们只接口温度、湿度、环境光和运动传感器。然而,执行功能仅限于蜂鸣器和 LED。您可以通过以下更改来提高项目的感官能力。
-
在实际场景中,一个远程家庭监控系统应该能够与家庭中的其他现有传感器接口,例如安全系统、监控摄像头、冰箱传感器、门传感器和车库传感器。
-
您还可以将此项目与其他家用电器如空调、加热器和安全报警器接口,这可以帮助您控制您已经监控的环境。作为试验,这些组件可以使用一组继电器和开关进行接口。
-
您可以使用更强大、更高效、更精确的传感器升级监控站上的当前传感器。然而,升级后的传感器监控站可能需要更强大的 Arduino 版本,具有更多的 I/O 引脚和计算能力。
-
您还可以在监控站使用除本项目使用的传感器以外的其他传感器。市面上有大量异构的、Arduino 支持的 DIY 传感器,您可以直接购买。这些传感器的例子包括酒精气体传感器(MQ-3)、液化石油气传感器(MQ-6)、一氧化碳传感器(MQ-7)、甲烷气体传感器(MQ-4)等等。这些传感器可以像我们之前连接的其他传感器一样简单地与 Arduino 接口。
-
为了适应这些变化,您将需要更改控制中心逻辑和算法。如果您正在接口第三方组件,您可能还需要重新审视系统架构并对其进行调整。
-
类似地,您还必须为额外的传感器频繁更新 Xively,这使得免费版本不足。为了解决这个问题,您可以支付 Xively 账户的商业版本或使用类似于以下代码片段显示的 JSON 文件格式进行有限数量的请求:
{ "version": "1.0.0", "datastreams": [ { "id": "example", "current_value": "333" }, { "id": "key", "current_value": "value" }, { "id": "datastream", "current_value": "1337" } ] }
改进 UX
当我们为这个项目设计用户体验时,我们的目标是展示 UX 设计在开发软件流程中的有用性。在当前的 UX 设计中,控制中心和网络应用对用户的控制和功能有限。以下是一些您需要实施以改进项目 UX 的更改:
-
为各种描述添加工具提示和适当的命名约定。实现适当的布局以区分不同的信息类别。
-
在控制中心 GUI 上添加蜂鸣器和 LED 控制的按钮。
-
在网络应用中,使用基于 JavaScript 和 Ajax 的界面来自动刷新传感器值的变化。
-
提供一个用户界面机制,以便用户可以在控制中心和网络应用程序中更改更新间隔。一旦做出这些更改,通过每个程序传播它们,以便监控站可以开始以新的间隔发布消息。
扩展基于云的功能
在当前的设置中,我们使用两个阶段来提供基于云的功能并启用远程监控。我们使用 Xively 作为数据中继,并使用 Amazon AWS 来托管网络应用程序。如果您正在开发商业级产品并希望简化架构的复杂性,您可以实施以下更改:
-
您可以使用如 ThingSpeak 等开源工具在您的云实例上开发自己的数据中继。然后,您的控制中心将直接与您的服务器通信,消除对第三方物联网服务的依赖。
-
如果 Xively 是您的平台,您还可以使用 Xively 提供的附加功能,例如智能手机上的图表。一旦您的手机与 Xively 配对,您可以直接访问此功能。
-
或者,您可以使用其他云服务,如 Microsoft Azure 和 Google App Engine,而不是 Amazon AWS。根据您对云计算的熟悉程度,您也可以设置自己的云服务器。尽管拥有自己的云将使您完全控制服务器,但与自托管服务器相比,第三方服务如 Amazon 可能更具成本效益且维护成本更低。
-
如果您计划开发基于当前架构的大型系统,您可以增加现有云实例的计算能力。您还可以实现分布式服务器系统,以适应大量远程监控系统,这些系统可以由更多的用户访问。
提高态势感知的智能程度
在这个项目中,我们使用了四种不同的传感器来监控物理环境——每个传感器都使用两种类型的执行器来获取用户输入以进行通知。尽管我们使用了大量的信息来源,但我们的态势感知算法仅限于识别超出范围的温度和湿度值。您可以通过实现一些扩展功能来使您的系统更加灵活和有用:
-
实现白天和夜晚场景的不同逻辑,这可以帮助您避免夜间不必要的误报。
-
使用运动传感器实现入侵检测算法,以便您不在家时使用。
-
利用环境光传感器值与运动传感器的组合来识别能源浪费。例如,当夜间运动显著减少时记录到的光线更多,这可能表明您在夜间忘记关闭灯光。
为硬件组件创建一个封装
就像基于软件的功能一样,如果你开发的是商业级产品,硬件组件也需要进行重大改造。如今,3D 打印机已经变得可行,设计和打印塑料 3D 组件变得非常容易。你也可以使用专业的 3D 打印服务,如 Shapeways (www.shapeways.com)、Sculpteo (www.sculpteo.com) 或 makexyz (www.makexyz.com) 来打印你的外壳。你甚至可以使用激光切割机或其他模型制作手段来创建硬件外壳。以下是一些你可以实施的硬件改进:
-
组装在原型板上的传感器和执行器可以被组织在 PCB 板上,并永久固定以实现稳定和坚固的操作。
-
监控站的外壳可以使设备便携,并易于在任何环境中部署。在设计这个外壳时,你还应考虑运动传感器和环境光传感器的适当位置,以及一个按钮,以便用户可以访问它们。
-
构成控制中心硬件的 Raspberry Pi 和 TFT 液晶显示屏也可以封装在一个可安装的包装中。
-
将触摸屏功能添加到 TFT 液晶显示屏上可以实现对系统的额外控制,扩展用户体验的使用案例。
摘要
在本章中,我们开发了一个远程家庭监控系统的工作原型,并同时学习了硬件产品开发的过程。在项目中,我们使用了本书中大部分的硬件组件和软件工具。我们首先设计系统架构,以便能够协调这些工具的利用。随后,我们进入了实际的开发阶段,这包括设计硬件单元和开发运行这些单元的程序。最后,我们提供了一份改进清单,以便将这个原型转变为真正的商业产品。欢迎你使用这种方法来开发你未来的项目和产品,因为你现在已经有了与这个项目合作的经验。
在最后一章,我们将利用相同的项目开发方法来创建一个有趣的项目,该项目利用来自社交网络网站的消息来控制你的硬件。
第十一章。推文电源条带
智能电源管理单元或条带是某些最受欢迎的物联网子领域的一部分,如智能家居和智能电网。如今,智能电源条带在商业上可用,并提供大量功能,如远程访问、智能电力使用和电力管理。在本项目中,我们将创建一个可以通过在 Twitter 上发布的状态消息远程控制的智能 DIY 电源条带,Twitter 是一个流行的社交媒体网站(www.twitter.com)。这些消息也被称为推文。基本上,就像您可以使用网页浏览器远程控制传感器一样,您也可以通过发送推文来控制它们。我们在上一个项目中已经与低功耗传感器合作过,所以在这个项目中让我们处理交流电器。我们将实施与上一个项目中使用的相同的项目开发方法。本章避免对过程的额外解释,仅关注与项目相关的细节。
项目概述
本项目需要使用 Arduino 和 Python 开发智能电源条带,而条带的控制输入是推文。尽管我们只启用了电源条带的远程访问功能,但未来可以实施大量其他功能,将这个 DIY 项目提升为商业产品。
我们在本项目中想要实现的主要目标是以下这些:
-
用户应能够使用定制的推文开关各个电源端口
-
用户应能够通过 Twitter 检查电源端口的状况
项目需求
这里是初始项目需求,来源于目标:
-
系统应具有与继电器接口的 110V(或 220V)交流电源端口。
-
基于 Arduino 的单元应能够控制这些继电器,最终控制通过电源端口连接的电器
-
系统应能够解码用户发送的推文,并将它们转换为 Arduino 的适当控制消息
-
基于 Python 的程序处理推文后,应发布这些消息,以便 Arduino 可以使用继电器完成这些操作。
-
总结来说,应使用用户发送的推文以近乎实时的方式控制继电器。
-
系统还应理解关键词以检查继电器的状态,并自动推文状态。系统应仅处理一次推文,并能够记住最后处理的推文。
注意
110V 与 220V 交流电源
根据国家不同,您的交流电源可能具有 110/120V 或 220/240V 的电压等级。尽管本项目使用的电路图提到了 110V 交流电源,但相同的电路也适用于 220V 电源。如果您使用 220V 电源,在继续之前请查看以下注意事项:
-
确保您要操作的电器,如风扇、灯具等,适用于类似的交流电源
-
您必须确保项目使用的继电器与您的交流电源兼容
-
Arduino 使用直流电源供电,不受交流电源任何变化的影响
-
系统架构
从前面的要求来看,让我们绘制 Tweet-a-PowerStrip 系统的架构图。系统架构试图利用您在前面章节中学到的硬件组件和软件工具,同时将继电器组件作为唯一的例外。如您在以下架构图中看到的,我们正在使用继电器来控制各种家用电器。这些电器通常由每个家庭都有的通用 110V 交流电源供电。我们不是控制单个电器,而是实现了一个四通道继电器,以控制至少四个电器,如灯、风扇、烤面包机和咖啡机。

该继电器通过 Arduino Uno 板上的数字引脚进行控制,该板使用以太网盾连接到您的家庭网络。一个可能由计算机、树莓派或服务器组成的计算单元,使用 Python 及其支持库来访问推文。计算单元还部署了一个 Mosquitto 代理。这个代理处理来自 Python 程序和 Arduino 的议题,以控制继电器。用户可以从任何平台(如手机或浏览器)发布包含关键词的推文,最终这些推文会被计算单元捕获。
所需硬件组件
在整个开发和部署阶段,该项目将需要以下硬件组件:
| 组件 | 数量 | 网站/备注 |
|---|---|---|
| Arduino Uno | 1 | www.sparkfun.com/products/11021 |
| Arduino 以太网盾 | 1 | www.sparkfun.com/products/9026 |
| 继电器(四通道,Arduino 兼容) | 1 | www.amazon.com/JBtek-Channel-Module-Arduino-Raspberry/dp/B00KTEN3TM/ |
| PowerSwitch Tail | 4 | www.powerswitchtail.com/继电器的替代品 |
| 电源插座 | 可选 | |
| 面包板 | 1 | 用于开发阶段 |
| Arduino USB 线 | 1 | 用于开发阶段 |
| Arduino 电源 | 1 | 用于部署阶段 |
| 电胶带 | 根据需求 | |
| 连接线 | 根据需求 |
继电器
如您在以下图片中看到的,我们引入了一个新的硬件组件,它在前面的章节中没有被使用——一个继电器:

这是一种电磁设备,通过使用电力作为开关来操作。一个典型的继电器在高压侧有三个触点,通常连接(NC),公共(C),和通常断开(NO)。继电器的另一侧(控制侧)需要激活电压来切换连接从公共-NC 到公共-NO。这个动作展示了高压侧连接的开关功能。我们将使用来自 Keyes 或 SainSmart 等制造商的 Arduino 兼容继电器。这些继电器有单通道、双通道或四通道配置。在高压侧,继电器支持高达 250V,10A 交流电或 30V,10A 直流电。继电器通过低功率侧的 5V 直流电控制,该电压由 Arduino 板上的数字 I/O 引脚提供。
PowerSwitch Tail
如果你之前没有处理过交流电或者不熟悉必要的预防措施和测量,那么与交流电一起工作可能会有危险。如果你不习惯于处理开启的继电器或将交流电连接到它们,你可以使用另一种设备来替换继电器——PowerSwitch Tail,这是一个安全封装的盒子,包含光隔离固态继电器,并提供了一种方便的方式将你的交流电器与 Arduino 板连接起来。以下是 PowerSwitch Tail 的图片,可以从其官方网站www.powerswitchtail.com/获取:

注意
如果你处理的是 220V/240V 电源,PowerSwitch Tail 网站还提供适用于 200V 至 240V 电源的组装套件,可在www.powerswitchtail.com/Pages/PowerSwitchTail240vackit.aspx找到。
根据提供的www.powerswitchtail.com/Documents/PSSRTK%20Instructions.pdf指南,组装这套设备非常简单。
对于这个项目,你需要四个这样的设备来替换我们即将使用的四通道继电器。正如以下图所示,Tail 的一端插入常规电源端口,而你需要将你的设备连接到另一个端口。同时,你可以使用三个控制输入来控制继电器。我们正在使用 Arduino 板上的一个数字 I/O 引脚将控制信号发送到 Tail。当使用 Tail 而不是继电器时,请确保对即将到来的硬件设计进行必要的修改。

用户体验流程
从我们创建的系统架构来看,在使用 Tweet-a-PowerStrip 时,用户体验(UX)流程应该是什么?我们将 UX 分为两个独立的部分:控制电器电源和检查电源插座的状态。
在第一个 UX 流程设计中,如以下图所示,用户首先发送包含设备名称(#风扇、#灯、#烤面包机或#咖啡)和控制命令(#开或#关)的推文。系统应该能够从解析推文到设备按请求行为处理整个流程。系统还应为用户提供无障碍体验,用户只需发送推文,无需执行任何其他操作。

同样,用户应该能够发布#状态 #检查推文,并简单地获取系统发布的状态报告。系统应处理检查电源端口的状态,将其发布到计算单元,并发布带有消息的推文,而无需用户任何额外的输入。
下图显示了检查系统状态的 UX 流程:

开发和部署阶段
根据架构,我们需要两个主要开发阶段来完成项目。第一个阶段,通过与继电器交互来与设备交互,使用 Arduino 进行开发。该单元订阅与设备相关的主题,一旦接收到适当的消息,它就会在继电器级别执行动作。在第二个阶段,我们处理单个推文,从 Twitter 账户解析推文,检查重复项,从消息中解码动作,并发布带有状态报告的推文。在这些开发阶段中,我们将使用面包板和跳线来测试 Arduino 和 Python 程序。在这个阶段,项目还没有准备好作为便携式单元用于日常使用。
部署阶段包括创建面包板连接的 PCB 和绝缘电线以避免任何电气危险的任务。你也可以购买或创建一个外壳箱来隔离开放硬件与物理接触。由于开发阶段包含将项目转换为工作状态所需的一切,我们不会深入探讨部署阶段。你可以根据个人需求执行额外的部署任务。
让我们从硬件设计阶段开始,使用 Arduino 开发智能电源插座的物理部分。
阶段 1 – 带有 Arduino 和继电器的智能电源插座
Tweet-a-PowerStrip 的硬件包含 Arduino 作为主控制器单元,它通过继电器和以太网盾与计算单元通信。Arduino 代码实现了 MQTT 客户端,使用PubSubClient库发布和订阅主题。尽管我们使用了一些示例设备来控制继电器的使用,但你也可以选择你拥有的任何其他设备。你也可以使用商业电源插座而不是单个电源插头。
硬件设计
在组装硬件组件时,如以下图所示,确保您在连接电器与交流电源插头时非常精确。交流插头的一根线直接连接到电器,而另一根线连接到继电器的 C 和 NO 端口之间。我们已经将继电器的控制端连接到我们的 Arduino 的数字引脚。由于我们使用的是四通道继电器,我们不得不利用 Arduino 板上的四个数字 IO 引脚。按照此处所示完成剩余的连接:

连接硬件单元相对简单,但需要很高的精度,因为它涉及到高功率的交流连接。
小贴士
您应该用绝缘胶带覆盖通往继电器和电器的 110V 交流电源线,以避免任何类型的电气危险。由于这些电线携带大量电流,保持这些电线裸露是非常危险的。在部署阶段,围绕继电器单元的塑料盖或盒子也可以帮助覆盖裸露的电源线。
一旦您完成了连接,请使用 USB 端口将 Arduino 板连接到您的计算机,如图所示:

Arduino 代码
本节的 Arduino 草图位于包含章节代码的文件夹中,文件名为Arduino_powerstrip.ino。您可以在 Arduino IDE 中打开该文件以探索代码。像往常一样,您必须将设备的 IP 地址和 Mosquitto 服务器的 IP 地址更改为适当的 IP 地址,同时更改以太网盾的 MAC 地址。以下代码片段显示了 Arduino 引脚的声明及其在主函数setup()中的作用,请确保您使用的是连接继电器时使用的相同引脚编号。或者,您可以将电器名称更改为您使用的电器名称。此外,请确保您对变量名称所做的任何更改都反映在整个代码中,以避免任何编译错误:
pinMode(FAN, OUTPUT);
pinMode(LAMP, OUTPUT);
pinMode(TOASTER, OUTPUT);
pinMode(COFFEEMAKER, OUTPUT);
fanStatus = false;
lampStatus = false;
toasterStatus = false;
coffeemakerStatus = false;
digitalWrite(FAN, LOW);
digitalWrite(LAMP,LOW);
digitalWrite(TOASTER, LOW);
digitalWrite(COFFEEMAKER, LOW);
在setup()函数中,代码还订阅了适当的 MQTT 通道,以便它可以在消息可用时立即从 Mosquitto 代理接收消息。如您所见,我们还在订阅PowerStrip/statuscheck通道以处理状态报告:
if (client.connect("PowerStrip")) {
client.subscribe("PowerStrip/fan");
client.subscribe("PowerStrip/lamp");
client.subscribe("PowerStrip/toaster");
client.subscribe("PowerStrip/coffeemaker");
client.subscribe("PowerStrip/statuscheck");
}
在callback()函数中,我们使用if语句来匹配主题与适当的digitalWrite()动作。如您所见,当程序接收到on和off消息时(针对该电器),我们正在为数字引脚设置HIGH和LOW状态。通过这个动作,我们还改变了与电器关联的布尔变量的状态,这将有助于检索端口的状况。然后,对所有的电器重复相同的过程:
if(topicS == "PowerStrip/fan"){
if (payloadS.equalsIgnoreCase("on")) {
digitalWrite(FAN, HIGH);
fanStatus = true;
}
if (payloadS.equalsIgnoreCase("off")){
digitalWrite(FAN, LOW);
fanStatus = false;
}
}
当系统接收到与状态检查相关的get消息时,程序使用我们之前切换过的布尔变量创建一条消息。然后程序将状态发布到PowerStrip/statusreport通道:
if(topicS.equals("PowerStrip/statuscheck")){
if (payloadS.equalsIgnoreCase("get")) {
String report = "";
if (fanStatus) report += "Fan:on,";
else report += "Fan:off,";
if (lampStatus) report += "Lamp:on,";
else report += "Lamp:off,";
if (toasterStatus) report += "Toaster:on,";
else report += "Toaster:off,";
if (coffeemakerStatus) report += "Coffeemaker:on";
else report += "Coffeemaker:off";
report.toCharArray(reportChar, 100);
client.publish("PowerStrip/statusreport", reportChar);
}
}
正如我们在上一个项目中做的那样,你可以设置代码定期发送keep alive消息以避免与 Mosquitto 代理的连接终止。一旦代码准备就绪,连接以太网电缆,编译代码,然后将它上传到你的 Arduino。现在你的 Arduino 应该处于接收模式,并等待来自订阅通道的消息。正如我们在上一个项目中讨论的那样,你需要确保你的 Mosquitto 代理正在运行在 Arduino 代码中指定的服务器 IP 地址上。
第二阶段 – 处理推文的 Python 代码
由于用户是在 Twitter 应用程序的级别与系统交互,我们不需要为这个项目部署可部署的计算或控制单元。因此,我们只需使用任何能够托管 Python 和 Mosquitto 的计算机作为计算单元即可。你仍然需要确保该单元始终开启并连接到互联网,否则系统将无法按预期工作。为了简单起见,你可以将系统部署在你之前项目开发过的基于 Raspberry-Pi 的控制中心上,甚至可以部署在 Amazon AWS 服务器上。对于开发阶段,让我们从你一直使用的普通计算机开始。我们假设这台计算机已经安装并运行了 Mosquitto 代理。记下这个单元的 IP 地址,因为你将在之前章节开发的 Arduino 代码中需要它。
Python 软件流程
Python 代码在执行过程中处理两个服务,分别是用于获取或发布推文的 Twitter API 和用于将消息中继到硬件单元的 Mosquitto 代理。程序首先解析用户账户的最新推文,并检查它是否在之前的操作中被使用过。这样可以避免任何命令重复,因为新推文的频率远低于程序循环的频率。一旦代码找到包含适当关键词的新推文,可以用来对设备(或设备组)进行操作,它就会将消息发布到 Mosquitto 代理。如果推文包含检查状态的指令,代码会从你的 Arduino 请求状态,并在收到状态后发布一条新的推文。
以下图表显示了计算单元的详细程序流程:

你可以更改程序流程以适应你想要在 Python 级别添加的任何其他功能。识别和切换设备背后的逻辑可以根据更复杂的推文文本进行改进。
设置 Twitter 应用程序
我们假设您现在已经有了一个 Twitter 账户。如果没有,您可以只为这个项目创建一个新账户,以避免更改您自己的个人资料。随着最新 API 的引入,Twitter 要求您在访问账户中的任何信息之前使用 OAuth 进行身份验证。为此,您将需要使用您的账户创建一个 Twitter 应用程序。按照以下步骤创建此项目的新的 Twitter 应用程序:
-
登录您的 Twitter 账户,并在您的网络浏览器中打开
apps.twitter.com地址。 -
在页面上点击创建新应用程序图标,您将被引导到一个页面,要求您提供应用程序详细信息,如下面的截图所示:
![设置 Twitter 应用程序]()
-
填写所有必需的详细信息(用红色星号标记),然后继续到下一页。请确保您的应用程序名称是唯一的,因为 Twitter 要求应用程序名称必须是唯一的。
-
一旦您的应用程序创建完成,您可以在API 密钥标签页中点击,找到您应用程序的消费者密钥(API 密钥)和消费者密钥(API 密钥)。请将此信息保存在安全的地方,因为您将需要它们来通过 Twitter API 进行身份验证。
![设置 Twitter 应用程序]()
-
由于 Tweet-a-PowerStrip 项目的用户体验要求系统自动发送系统状态,我们需要对我们的应用程序有读写访问权限。转到权限标签页,选择读取和写入选项,并保存更改以生效。
![设置 Twitter 应用程序]()
-
一旦您完成了设置应用程序权限,返回到 API 密钥标签页,并点击创建访问令牌图标以生成此应用程序的新访问令牌。一段时间后,您应该能在同一页面上看到访问令牌,如下面的截图所示:
![设置 Twitter 应用程序]()
-
保存访问令牌和访问令牌密钥信息。您的应用程序现在可以使用了,并且可以帮助您通过 Twitter API 进行身份验证。
现在让我们继续到 Python 代码部分。
Python 代码
在您开始编写代码之前,您需要安装 Python 的 Twitter 库。使用 Setuptools 或pip通过以下命令安装库。我们假设您已经在您的计算机上安装了最新的paho_mqtt库:
$ sudo pip install python-twitter
本节中的 Python 代码位于代码文件夹中,文件名为PythonTweetAPowerStrip.py。在您的 IDE 中打开代码,并开始探索它。该代码包含两个并行线程,分别处理推文和 Mosquitto 库。
如以下代码片段所示,我们正在使用python-twitter库中的Api类与 Twitter API 建立连接。我们使用consumer key、consumer secret、access token key和access token secret值进行此认证。一旦建立认证,就可以使用Api类通过GetHomeTimeline()函数调用来获取时间线上的最新状态,以及通过PostUpdate()函数调用来发布新状态。GetHomeTimeline()函数返回用户的状态数组;我们需要获取最新状态,可以使用statuses[0](数组的第一个元素)来获取:
api = twitter.Api(consumer_key='<consumer-key>',
consumer_secret='<consumer-secret>',
access_token_key='<access-token-key>',
access_token_secret='access-token-secret>')
一旦我们检索到最新的推文,我们需要确保我们没有使用过那条推文。因此,我们将最新的推文 ID 保存到全局变量中,以及在一个文件中,以防我们需要再次运行代码:
with open('lastTweetID.txt', 'w+') as fh:
lastTweetId = fh.readline()
print "Initializing with ID: " + lastTweetId
我们从lastTweetID.txt文件中检索上一条推文的 ID,以与最新 ID 进行匹配。如果不匹配,我们将最新 ID 更新到lastTweetID.txt文件中,以便下一次循环:
if lastTweetId != str(currentStatus.id):
lastTweetId = str(currentStatus.id)
print "Updated file with ID: " + lastTweetId
with open('lastTweetID.txt', 'w+') as fh:
fh.write(lastTweetId)
currentStatusText = currentStatus.text
print currentStatusText
一旦我们确定了最新的唯一推文,我们使用 Python 字符串操作来解码设备和控制命令的关键字。如以下代码片段所示,我们在推文文本中寻找的关键字以访问风扇是#fan。一旦我们确定消息是针对风扇的,我们检查动作关键字,如#on和#off,然后执行将消息发布到 Mosquitto 代理的相关动作。我们对所有连接到系统的设备重复此动作。你的 Arduino 使用发布的消息采取行动,并完成控制设备的用户体验流程:
if "#fan" in currentStatusText.lower():
if "#on" in currentStatusText.lower():
cli.publish("PowerStrip/fan", "on")
if "#off" in currentStatusText.lower():
cli.publish("PowerStrip/fan", "off")
同样,当代码从PowerStrip/statusreport主题接收到更新时,它从消息有效负载中获取状态,并将其作为新推文发布到该 Twitter 账户的用户时间线。这完成了使用 Twitter 进行状态检查的用户体验流程:
def onMessage(mosq, obj, msg):
if msg.topic == "PowerStrip/statusreport":
print msg.payload
api.PostUpdate(msg.payload)
测试和故障排除
测试可以通过将#fan #on状态发布到本项目使用的 Twitter 账户来简单进行。你应该能够通过以下命令看到风扇开启:

同样,发送#fan #off状态来关闭风扇。你可能会发现一些延迟,因为用于检索推文的循环被设置为每分钟延迟。

要访问系统状态,将#status #get状态发布到账户,你将能够看到计算单元自动发布的系统状态。

下面的截图显示的推文是使用 Tweet-a-PowerStrip 单元生成的。它显示了所有连接的设备的状态。

在使用系统时,您可能希望避免以下场景或对其进行故障排除:
-
'Twitter 速率限制超出'错误:Twitter 对其公共 API 的请求数量有限制。如果您请求 API 过于频繁(这通常发生在您减少连续查询之间的睡眠时间时),您的应用程序将异常退出。为了避免这种情况,在请求 API 之前,在 Python 程序循环中设置更长的睡眠时间。请求频率和电器响应时间之间存在权衡。您可以在dev.twitter.com/rest/public/rate-limiting上了解此限制,并相应地调整请求间隔。一旦收到此错误,您将不得不等待一段时间(大约 10 到 15 分钟)才能再次向 Twitter API 发送请求。 -
'只读应用程序无法发布'错误:此错误仅会在您忘记将应用程序的权限从只读更改为读和写时发生。请确保您已执行此更改。此外,Twitter 需要一些时间来使更改生效。
通过添加额外功能扩展项目
当前系统可以扩展以包括多个功能:
-
您可以开始记录特定电器开启或关闭的时间段,并向用户提供详细的分析。您还可以使用这些信息来计算这些电器消耗的能量。
-
您可以利用当前的测量传感器来计算每个端口的电力负载。结合设备开启的时间,您可以计算出非常全面的电力使用情况,以进一步改善电力管理。
-
您可以使用系统时钟和运动传感器在夜间和无活动期间智能地关闭电器。
-
Tweet-a-PowerStrip 项目可以与我们在上一个项目中开发的远程家庭监控系统接口,以便从同一房屋中使用的其他传感器获取有用信息。
-
您可以轻松实现的修改之一是利用 Twitter 的私信而不是其推文来控制电器。这将扩展您系统的访问权限,使其能够访问其他受信任的 Twitter 账户。出于安全原因,您应该提高访问级别,并仅允许经过批准的人向您的账户发布此类消息。
摘要
您现在已成功使用仅两种基础技术,Arduino 和 Python,完成了两个不同的物联网项目。随着当前项目,很明显,将任何其他技术、工具或 API 与 Arduino 和 Python 接口非常容易。我们在这些两个项目中使用的项目开发方法也将帮助您进行 DIY 项目和未来的其他产品。祝您原型制作愉快!祝您编码愉快!








































浙公网安备 33010602011771号