Python-物联网项目-全-

Python 物联网项目(全)

原文:zh.annas-archive.org/md5/34135f16ce1c2c69e5f81139e996b460

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

物联网承诺解锁真实世界,就像互联网几十年前解锁了数百万台计算机一样。树莓派计算机于 2012 年首次发布,迅速风靡全球。最初设计的目的是给新一代带来与上世纪 80 年代个人计算机同样的编程激情,树莓派已经成为无数创客的标配。

1991 年,Guido van Rossum 向世界介绍了 Python 编程语言。Python 是一种简洁的语言,旨在提高代码可读性。Python 程序往往需要的代码行数比其他编程语言少。Python 是一种可扩展的语言,可用于从最简单的程序到大规模项目。

在本书中,我们将释放树莓派和 Python 的力量,创建令人兴奋的物联网项目。

本书的第一部分向读者介绍了令人惊叹的树莓派。我们将学习如何设置它,并立即开始 Python 编程。我们将通过为物理计算创建“Hello World”应用程序——闪烁 LED,开始我们的真实计算之旅。

我们的第一个项目将我们带回到模拟指针仪表统治数据显示领域的年代。回想一下那些旧的模拟万用表和无数的旧科幻电影,信息是通过按钮和大型闪烁灯控制和显示的。在我们的项目中,我们将从网络服务中检索天气数据,并在模拟指针仪表上显示它。我们将通过 GPIO 将舵机连接到我们的树莓派,实现这一目标。

家庭安全系统在现代生活中几乎无处不在。整个行业和职业都建立在安装和监控这些系统上。你知道吗,你可以轻松地创建自己的家庭安全系统吗?在我们的第二个项目中,我们就是这样做的,我们使用树莓派作为 Web 服务器来构建家庭安全系统。

自 1831 年以来,谦卑的门铃一直与我们同在。在我们的第三个项目中,我们将给它增加 21 世纪的变化,让我们的树莓派向网络服务发送信号,当有人来敲门时,网络服务将给我们发短信。

在我们的最后一个项目中,我们将从前两个项目中学到的知识创建一个名为 T.A.R.A.S(这个令人惊叹的树莓派自动安全代理)的物联网机器人车。

未来,无人驾驶汽车将成为规则而不是例外,需要一种控制这些汽车的方式。这个最后的项目为读者提供了洞察和知识,了解如何控制没有人类驾驶员的汽车。

这本书是为谁写的

本书面向那些对编程有一定了解并对物联网感兴趣的人。了解 Python 编程语言将是一个明显的优势。对面向对象编程的理解或浓厚的兴趣将有助于读者理解本书中使用的编码示例。

本书内容

第一章《在树莓派上安装 Raspbian》通过在树莓派上安装 Raspbian 操作系统开始了我们的树莓派物联网之旅。然后我们将看一些预装在 Raspbian 上的程序。

第二章《使用树莓派编写 Python 程序》介绍了 Windows、macOS 和 Linux 这些对开发人员来说很熟悉的操作系统。许多关于开发树莓派的书籍都涉及使用其中一个操作系统并远程访问树莓派。但本书将采用不同的方法,我们将把树莓派作为开发机器。在本章中,我们将初步了解如何将树莓派作为开发机器。

第三章,“使用 GPIO 连接外部世界”,解释了如果树莓派只是一台 35 美元的计算机,对我们许多人来说已经足够了。然而,树莓派背后真正的力量在于开发者通过通用输入输出GPIO)引脚访问外部世界的能力。在本章中,我们将深入研究 GPIO,并开始将树莓派连接到现实世界。我们将使用外部 LED 创建一个莫尔斯电码生成器,然后使用这个生成器来闪烁模拟的天气信息。

第四章,“订阅 Web 服务”,探讨了一些世界上一些最大公司提供的一些网络服务。我们的项目将使用虚拟版本的树莓派 Sense HAT 作为滚动条,显示来自 Yahoo! Weather 网络服务的当前天气信息。

第五章,“使用 Python 控制舵机”,介绍了使用连接到树莓派的舵机电机创建模拟仪表针的概念。

第六章,“使用舵机控制代码控制模拟设备”,继续讨论使用舵机电机的主题,因为我们正在构建我们的第一个真正的物联网设备,一个天气仪表盘。这个天气仪表盘不仅会有一个模拟指针;它还将使用指针指向根据天气条件建议的衣柜图片。

第七章,“设置树莓派 Web 服务器”,介绍了如何安装和配置 Web 框架 CherryPy。我们将通过构建一个显示天气信息的本地网站来结束本章。

第八章,“使用 Python 读取树莓派 GPIO 传感器数据”,介绍了如何在转移到 PIR 传感器和距离传感器之前读取按钮的状态。我们将通过构建简单的报警系统来结束本章。

第九章,“构建家庭安全仪表盘”,解释了如何使用树莓派作为提供从 GPIO 收集的传感器数据的 HTML 内容的 Web 服务器来构建家庭安全仪表盘。

第十章,“发布到 Web 服务”,介绍了如何测量室温和湿度,并通过物联网仪表板将这些值发布到网络上。我们还将设置并运行使用 Twilio 服务的短信警报。

第十一章,“使用蓝牙创建门铃按钮”,将我们的重点转向本章中的蓝牙使用。蓝牙是一种无线技术,允许在短距离内传输数据。对于我们的项目,我们将探索 Android Play 商店中的 BlueDot 应用。我们将使用这个应用来构建一个简单的蓝牙连接门铃。

第十二章,“增强我们的物联网门铃”,将我们在“使用蓝牙创建门铃按钮”中创建的简单门铃转变为物联网门铃,使用我们在“发布到 Web 服务”中学到的知识。

第十三章,“介绍树莓派机器人车”,通过介绍这个令人惊叹的树莓派自动安全代理(T.A.R.A.S)开始了我们进入物联网机器人车的旅程。本章将首先概述我们构建 T.A.R.A.S 所需的组件,然后我们将继续将它们全部组装起来。

第十四章,使用 Python 控制机器人车,介绍了如何为我们的机器人车编写 Python 代码。我们将利用 GPIO Zero 库使车轮向前转动,移动携带摄像头的伺服电机,并点亮机器人车后面的 LED 灯。

第十五章,将机器人车的感应输入连接到网络,帮助我们理解,为了将我们的机器人车变成真正的物联网设备,我们必须将其连接到互联网。在本章中,我们将把机器人车的距离传感器连接到互联网。

第十六章,通过 Web 服务调用控制机器人车,继续将我们的机器人车变成物联网设备,深入研究了我们为机器人车创建的互联网仪表板。

第十七章,构建 JavaScript 客户端,将我们的注意力从 Python 转移到 JavaScript。我们将使用 JavaScript 构建一个基于 Web 的客户端,使用 MQTT 协议在互联网上进行通信。

第十八章,将所有内容整合在一起,介绍了我们将如何将我们的机器人车 T.A.R.A.S 连接到 JavaScript 客户端,并使用 MQTT 协议在互联网上进行控制。

为了充分利用本书

为了充分利用本书,我将假设以下情况:

  • 您已经购买或将购买一台树莓派计算机,最好是 2015 年或更新的型号。

  • 您对 Python 编程语言有一定了解,或者渴望学习它。

  • 您对电子元件有基本的了解,并知道如何使用面包板。

  • 您已经购买或愿意购买基本的电子元件。

在硬件需求方面,您至少需要以下设备:

  • 一个树莓派 3 型号(2015 年或更新的型号)

  • 一个 USB 电源适配器

  • 一台计算机显示器

  • 一个 USB 键盘

  • 一个 USB 鼠标

  • 一张 microSD RAM 卡

  • 一个面包板和面包板跳线

每章节开始时会介绍额外的硬件部件。

在软件需求方面,您将需要树莓派 NOOBS 镜像(www.raspberrypi.org/downloads/noobs/)。额外的软件、账户和 Python 包将在途中介绍。本书中使用的任何软件、网络服务或 Python 包都是免费的。

下载示例代码文件

您可以从您在www.packt.com的账户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packt.com/support并注册,以便直接通过电子邮件接收文件。

您可以按照以下步骤下载代码文件:

  1. www.packt.com上登录或注册。

  2. 选择“支持”选项卡。

  3. 点击“代码下载和勘误”。

  4. 在“搜索”框中输入书名,然后按照屏幕上的说明操作。

下载文件后,请确保使用以下最新版本解压或提取文件夹:

  • Windows 上的 WinRAR/7-Zip

  • Mac 上的 Zipeg/iZip/UnRarX

  • Linux 上的 7-Zip/PeaZip

本书的代码捆绑包也托管在 GitHub 上,网址为github.com/PacktPublishing/Internet-of-Things-Programming-Projects。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还有其他代码捆绑包,来自我们丰富的图书和视频目录,可在github.com/PacktPublishing/上找到。去看看吧!

下载彩色图片

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781789134803_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。这是一个例子:“为了访问 Python 3,我们在终端窗口中输入  python3 命令。”

代码块设置如下:

wind_dir_str_len = 2
if currentWeather.getWindSpeed()[-2:-1] == ' ':
    wind_dir_str_len = 1

任何命令行输入或输出都以以下方式编写:

pip3 install weather-api

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这是一个例子:“从 视图 菜单中,选择 对象检查器 和 变量。”

警告或重要说明会以这种方式出现。提示和技巧会以这种方式出现。

第一章:在树莓派上安装 Raspbian

树莓派被宣传为一台小巧实惠的计算机,您可以用来学习编程。至少这是它最初的目标。正如我们将在本书中看到的,它远不止于此。

本章将涵盖以下主题:

  • 树莓派的简要历史

  • 树莓派的操作系统

  • 安装 Raspbian 操作系统

  • Raspbian 操作系统的快速概述

树莓派的简要历史

首次发布于 2012 年,第一代树莓派配备了 700 MHz 单核处理器和 256 MB 内存。树莓派 2 于 2015 年 2 月发布,配备了 900 MHz 四核处理器和 1 GB 内存。树莓派 3 于 2016 年 2 月发布,将处理器速度提高到 1.2 GHz。这款型号还是第一款包含无线局域网和蓝牙的型号。

以下是 2015 年树莓派 3 B 的图片:

这个版本的树莓派包括以下部分:

  • 四个 USB 2 端口

  • 一个 LAN 端口

  • 一个 3.5 毫米复合视频和音频插孔

  • 用于视频和音频的 HDMI 端口

  • 一个 OTG USB 端口(我们将用它连接电源)

  • 一个 microSD 插槽(用于放置我们的操作系统)

  • 用于树莓派触摸屏的 DSI 显示端口

  • 通用输入输出(GPIO)引脚

  • 一个用于特殊树莓派摄像头的摄像头端口

树莓派 Zero 于 2015 年 11 月发布。以下是它的图片:

尽管不如之前的树莓派强大,Zero 的尺寸更小(65 毫米 X 30 毫米),非常适合空间有限的项目(即可穿戴项目)。此外,树莓派 Zero 的价格为 5 美元,非常实惠。树莓派 Zero W 于 2017 年 2 月 28 日发布,价格翻倍(10 美元),内置 Wi-Fi 和蓝牙功能。

截至撰写本文时,最新型号是于 2018 年 3 月 14 日发布的树莓派 3 B+。处理器速度已升级至 1.4 GHz,无线局域网现在支持 2.4 GHz 和 5 GHz 频段。另一个升级是增加了低功耗蓝牙,这是一种为不需要大量数据交换但需要长电池寿命的应用程序而设计的技术。

树莓派的创造者最初认为他们最多只能卖出 1000 台。他们不知道他们的发明会爆炸式地受欢迎。截至 2018 年 3 月,树莓派计算机的销量已经超过了 1900 万台。

树莓派的操作系统

可以安装在树莓派上的各种操作系统(或系统镜像)从特定应用程序的操作系统,如音频播放器,到各种通用操作系统。树莓派的强大之处在于它可以用于各种应用和项目。

以下是一些适用于树莓派的操作系统(系统镜像)的列表:

  • Volumio:您是否想要建立一个网络音频系统,通过计算机或手机访问您的音乐列表?Volumio 可能是您正在寻找的东西。在树莓派上安装它可以创建一个无头音频播放器(不需要键盘和鼠标的系统),通过 USB 或网络连接到您的音频文件。可以添加一个特殊的音频 HAT(硬件附加在顶部)到您的 Pi 上,以提供纯净的音频连接到放大器和扬声器。甚至有一个插件可以添加 Spotify,这样您就可以设置您的树莓派访问这项服务,并在您的音响系统上播放音乐。

  • PiFM 无线电发射器:PiFM 无线电发射器将您的树莓派变成 FM 发射器,您可以使用它将音频文件通过空气发送到标准 FM 收音机。通过连接到 GPIO 引脚之一的简单导线(我们稍后将了解更多关于 GPIO 的知识),您可以为传输的 FM 信号创建天线,这个信号出奇地强。

  • Stratux:ADS-B 是航空领域的新标准,其中地理位置和天气信息与地面控制器和飞行员共享。 Stratux 镜像与附加硬件将树莓派变成这些信息的 ADS-B 接收器。

  • RetroPie:RetroPie 将您的树莓派变成一个复古游戏主机,通过模拟过去的游戏主机和计算机。一些模拟包括 Amiga,Apple II,Atari 2600 和 20 世纪 80 年代初的任天堂娱乐系统。

  • OctoPi:OctoPi 将您的树莓派变成 3D 打印机的服务器。通过 OctoPi,您可以通过网络控制您的 3D 打印机,包括使用网络摄像头查看您的 3D 打印机的状态。

  • NOOBS:这可能是在树莓派上安装操作系统的最简单方法。NOOBS 代表 New Out-Of-the Box Software,我们将使用 NOOBS 来安装 Raspbian。

项目概述

在这个项目中,我们将在我们的树莓派上安装 Raspbian 操作系统。安装完成后,我们将快速浏览操作系统以熟悉它。我们将首先格式化一个 microSD 卡来存储我们的安装文件。然后我们将从 microSD 卡运行安装。Raspbian 安装完成后,我们将快速浏览一下以熟悉它。

这个项目应该需要大约两个小时来完成,因为我们安装 Raspbian 操作系统并快速浏览一下。

入门

完成此项目需要以下内容:

  • 一个树莓派 3 型(2015 年或更新型号)

  • USB 电源适配器

  • 计算机显示器

  • USB 键盘

  • USB 鼠标

  • 一个 microSD RAM 卡

  • 树莓派 NOOBS 镜像(www.raspberrypi.org/downloads/noobs/

安装 Raspbian 操作系统

Raspbian 操作系统被认为是树莓派的默认操作系统。在本节中,我们将使用 NOOBS 镜像安装 Raspbian。

为 Raspbian 格式化 microSD 卡

树莓派使用 microSD 卡存储操作系统。这使您可以轻松地在不同的操作系统(系统镜像)之间切换,用于您的树莓派。我们将使用 NOOBS 镜像为我们的项目安装默认的 Raspbian 操作系统。

首先将 microSD 卡插入 USB 适配器,然后插入计算机:

您可能需要格式化 microSD 卡。如果需要,使用适合您计算机操作系统的工具将卡格式化为 FAT32。建议使用容量为 8GB 或更大的卡。对于 Windows 操作系统和容量为 64GB 或更大的卡,应使用第三方工具(如 FAT32 格式)进行格式化。

将 NOOBS 文件复制到 microSD RAM

解压您下载的 NOOBS 镜像。打开解压后的目录,将文件拖到 microSD 卡上。

文件应该与以下截图中的一样:

运行安装程序

现在我们将在树莓派上安装 Raspbian。这一步骤对于之前有安装 Windows 或 macOS 等操作系统经验的人来说应该很熟悉。Raspbian 操作系统将被安装并在我们的 microSD 卡上运行。

要在我们的 microSD 卡上安装 Raspbian,请执行以下操作:

  1. 首先将 microSD 卡插入 Raspberry Pi 上的适当插槽。 请确保安装时标签面(暴露的接触面的对面)朝上。 将其与金属接触面朝向板子插入。 microSD 卡的标签面顶部应该有一个微小的凸起,方便用指甲轻松取出。

  2. 将键盘和鼠标插入侧面的 USB 插槽,将显示器插入 HDMI 端口,最后将 USB 电源线插入电源端口。 Raspberry Pi 没有开关,只要连接电源线,它就会启动:

  1. 在初始的黑屏上滚动白色文本后,您应该会看到以下对话框:

  1. 在上一个屏幕截图中,我们点击了语言选项。 对于我们的目的,我们将保持默认的英语(英国)。 我们还将保持标准的 gb 键盘。

  2. 由于 Raspberry Pi 3 具有无线局域网,我们可以设置我们的 Wi-Fi(对于较旧的板,请将 Wi-Fi dongle 插入 USB 端口或使用有线 LAN 端口并跳过下一步):

  1. 单击 Wifi 网络(w)按钮。 使用单选按钮选择认证方法。 一些路由器配备了 WPS 按钮,可让您直接连接到路由器。 要使用“密码”方法,请选择密码认证单选按钮,并输入网络的密码。 连接到网络后,您将注意到现在有更多的操作系统选项可供选择:

  1. 我们将选择顶部选项 Raspbian。 在 Raspbian [RECOMMENDED]旁边勾选框,然后单击对话框左上角的安装(i)按钮。 Raspbian 将开始安装在您的 Raspberry Pi 上。 您将看到一个带有先前图形的进度条,描述 Raspbian 操作系统的各种功能:

  1. 进度条达到 100%后,计算机将重新启动,然后您将看到一个屏幕上的文本,然后默认桌面加载:

Raspbian OS 的快速概述

Raspbian 桌面与其他操作系统(如 Windows 和 macOS)的桌面类似。 点击左上角的按钮会弹出应用程序菜单,您可以在其中访问各种预安装的程序。 我们还可以从此菜单关闭 Raspberry Pi:

Chromium 网络浏览器

从左边数第二个按钮加载 Raspberry Pi 的 Google Chromium 网络浏览器:

Chromium 浏览器是一款轻量级浏览器,在 Raspberry Pi 上运行非常出色:

home 文件夹

双文件夹按钮打开一个窗口,显示home文件夹:

home文件夹是在 Raspberry Pi 上查找文件的好地方。 实际上,当您使用scrot命令或 Print Screen 按钮进行截图时,文件会自动存储在此文件夹中:

终端

从左边数第三个按钮打开终端。 终端允许命令行访问 Raspberry Pi 的文件和程序:

就是从命令行中,您可以使用sudo apt-get updatesudo apt-get dist-upgrade命令来更新 Raspberry Pi。

apt-get更新软件包列表,apt-get dist-upgrade更新软件包:

在安装 Raspbian 后,建议立即运行这两个命令,使用sudo命令。Raspberry Pi 上 Raspbian 的默认用户是pi,属于 Raspbian 中的超级用户组,因此必须使用sudo命令(pi用户的默认密码是raspberry):

掌握命令行是许多程序员渴望掌握的一种技能。能够快速输入命令看起来很酷,甚至连电影制作人也注意到了(你上次看到电影中的电脑高手用鼠标在屏幕上点击是什么时候?)。为了帮助你成为这样一个超酷的电脑高手,这里有一些基本的 Raspbian 命令供你在终端中掌握:

ls:查看当前目录内容的命令

cd:切换目录的命令。例如,使用cd从当前目录上移一个目录

pwd:显示当前目录的命令

sudo:允许用户以超级用户的身份执行任务

shutdown:允许用户从终端命令行关闭计算机的命令

Mathematica

第三和第四个按钮分别用于 Mathematica 和访问 Wolfram 语言的终端:

Mathematica 涵盖技术计算的各个领域,并使用 Wolfram 语言作为编程语言。Mathematica 的应用领域包括机器学习、图像处理、神经网络和数据科学:

Mathematica 是一款专有软件,于 1988 年首次发布,通过 2013 年底宣布的合作伙伴关系,可以在 Raspberry Pi 上免费使用个人版。

现在让我们来看一些从主下拉菜单中访问的程序。

Sonic Pi

Sonic Pi 是一个用于创建电子音乐的实时编码环境。可以从编程菜单选项中访问。Sonic Pi 是一种创造音乐的创新方式,用户可以通过实时剪切和粘贴代码来编写循环、琶音和音景。Sonic Pi 中的合成器可以进行深层配置,为音乐编码者提供定制体验:

Sonic Pi 主要面向电子舞曲风格的音乐,也可以用来创作古典和爵士音乐风格。

Scratch 和 Scratch 2.0

Scratch 和 Scratch 2.0 是为教授儿童编程而设计的可视化编程环境。使用 Scratch,程序员可以创建自己的动画,并使用循环和条件语句。

程序中可以创建游戏。Scratch 的第一个版本于 2003 年由麻省理工学院媒体实验室的终身幼儿园小组发布。Scratch 2.0 于 2013 年发布,目前正在开发 Scratch 3.0:

Scratch 和 Scratch 2.0 可以在编程菜单选项下访问。

LibreOffice

LibreOffice 是一个免费开源的办公套件,于 2010 年从 OpenOffice 分支出来。LibreOffice 套件包括文字处理程序、电子表格程序、演示程序、矢量图形编辑器、用于创建和编辑数学公式的程序以及数据库管理程序。可以通过 LibreOffice 菜单选项访问 LibreOffice 程序套件:

总结

我们从 Raspberry Pi 的历史开始了本章。最初是为了推广编程教育给新一代人,现在已经发展成为一个全球现象。然后我们下载了 NOOBS 镜像并安装了 Raspbian 操作系统,这是 Raspberry Pi 的默认操作系统。这涉及格式化和准备 microSD 卡以安装 NOOBS 文件。

最容易认为像树莓派这样便宜小巧的计算机并不那么强大。我们证明了树莓派确实是一台非常有能力的计算机,因为我们看了一些预装在 Raspbian OS 上的应用程序。

在第二章中,使用树莓派编写 Python 程序,我们将开始使用树莓派和 Raspbian 中提供的一些开发工具进行 Python 编码。

问题

  1. 第一款树莓派是在哪一年推出的?

  2. 树莓派 3 Model B+相比上一个版本有哪些升级?

  3. NOOBS 代表什么?

  4. 预装应用程序的名称是什么,它允许使用 Python 代码创建音乐?

  5. 树莓派的操作系统存储在哪里?

  6. 为儿童设计的可视化编程环境的名称是什么,它预装在 Raspbian 中?

  7. Mathematica 中使用的语言名称是什么?

  8. Raspbian 的默认用户名和密码是什么?

  9. GPIO 代表什么?

  10. RetroPie 是什么?

  11. 真或假?单击主栏上的两个文件夹图标会加载“主目录”文件夹。

  12. 真或假?microSD 卡槽位于树莓派的底部。

  13. 真或假?要关闭树莓派,从应用程序菜单中选择关闭。

  14. 真或假?只能使用 NOOBS 安装 Raspbian OS。

  15. 真或假?蓝牙低功耗是指吃了太多蓝莓并且早上很难醒来的人。

进一步阅读

有关树莓派的更多信息,请参阅www.raspberrypi.org上的主要树莓派网站。

第二章:使用树莓派编写 Python 程序

在本章中,我们将开始使用树莓派编写 Python 程序。Python 是树莓派的官方编程语言,并由 Pi 代表在名称中。

本章将涵盖以下主题:

  • 树莓派的 Python 工具

  • 使用 Python 命令行

  • 编写一个简单的 Python 程序

Python 在 Raspbian 上预装了两个版本,分别是版本 2.7.14 和 3.6.5(截至目前为止),分别代表 Python 2 和 Python 3。这两个版本之间的区别超出了本书的范围。在本书中,我们将使用 Python 3,除非另有说明。

项目概述

在这个项目中,我们将熟悉树莓派上的 Python 开发。您可能已经习惯了在其他系统(如 Windows、macOS 和 Linux)上使用的开发工具或集成开发环境(IDE)。在本章中,我们将开始使用树莓派作为开发机器。随着我们开始使用 Python,我们将慢慢熟悉开发。

技术要求

完成此项目需要以下内容:

  • 树莓派 3 型号(2015 年或更新型号)

  • USB 电源供应

  • 计算机显示器

  • USB 键盘

  • USB 鼠标

树莓派的 Python 工具

以下是预装的工具,我们可以在树莓派上使用 Raspbian 进行 Python 开发。这个列表绝不是我们可以用于开发的唯一工具。

终端

由于 Python 预装在 Raspbian 上,启动它的简单方法是使用终端。如下面的屏幕截图所示,可以通过在终端窗口中输入python作为命令提示符来访问 Python 解释器:

我们可以通过运行最简单的程序来测试它:

print 'hello'

注意命令后的 Python 版本,2.7.13。在 Raspbian 中,python命令与 Python 2 绑定。为了访问 Python 3,我们必须在终端窗口中输入python3命令:

集成开发和学习环境

自从版本 1.5.2 起,集成开发和学习环境IDLE)一直是 Python 的默认 IDE。它本身是用 Python 编写的,使用 Tkinter GUI 工具包,并且旨在成为初学者的简单 IDE:

IDLE 具有多窗口文本编辑器,具有自动完成、语法高亮和智能缩进。对于使用过 Python 的任何人来说,IDLE 应该是很熟悉的。在 Raspbian 中有两个版本的 IDLE,一个用于 Python 2,另一个用于 Python 3。这两个程序都可以从应用程序菜单 | 编程中访问。

Thonny

Thonny 是随 Raspbian 捆绑的 IDE。使用 Thonny,我们可以使用debug函数评估表达式。Thonny 也适用于 macOS 和 Windows。

要加载 Thonny,转到应用程序菜单 | 编程 | Thonny:

上面是 Thonny 的默认屏幕。可以从“视图”菜单中打开和关闭查看程序中的变量的面板,以及查看文件系统的面板。Thonny 的紧凑结构使其非常适合我们的项目。

随着我们继续阅读本书的其余部分,我们将更多地了解 Thonny。

使用 Python 命令行

让我们开始编写一些代码。每当我开始使用新的操作系统进行开发时,我都喜欢回顾一些基础知识,以便重新熟悉(我特别是在凌晨熬夜编码的时候)。

从终端最简单地访问 Python。我们将运行一个简单的程序来开始。从主工具栏加载终端,然后在提示符处输入python3。输入以下行并按Enter

from datetime import datetime

这行代码将datetime模块中的datetime对象加载到我们的 Python 实例中。接下来输入以下内容并按Enter

print(datetime.now())

你应该看到当前日期和时间被打印到屏幕上:

让我们再试一个例子。在 shell 中输入以下内容:

import pyjokes

这是一个用来讲编程笑话的库。要打印一个笑话,输入以下内容并按Enter

pyjokes.get_joke()

你应该看到以下输出:

好的,也许这不是你的菜(对于 Java 程序员来说,也许是咖啡)。然而,这个例子展示了导入 Python 模块并利用它是多么容易。

如果你收到ImportError,那是因为pyjokes没有预先安装在你的操作系统版本中。类似以下例子,输入sudo pip3 install pyjokes将会在你的树莓派上安装pyjokes

这些 Python 模块的共同之处在于它们可以供我们使用。我们只需要直接将它们导入到 shell 中以便使用,因为它们已经预先安装在我们的 Raspbian 操作系统中。但是,那些未安装的库呢?

让我们试一个例子。在 Python shell 中,输入以下内容并按Enter

import weather

你应该看到以下内容:

由于weather包没有安装在我们的树莓派上,我们在尝试导入时会收到错误。为了安装这个包,我们使用 Python 命令行实用程序pip,或者在我们的情况下,使用pip3来进行 Python 3:

  1. 打开一个新的终端(确保你在终端会话中,而不是 Python shell 中)。输入以下内容:
pip3 install weather-api
  1. Enter。你会看到以下内容:

  1. 进程完成后,我们将在树莓派上安装weather-api包。这个包将允许我们从 Yahoo! Weather 获取天气信息。

现在让我们试一些例子:

  1. 输入python3并按Enter。现在你应该回到 Python shell 中了。

  2. 输入以下内容并按Enter

from weather import Weather 
from weather import Unit
  1. 我们已经从weather中导入了WeatherUnit。输入以下内容并按Enter
 weather = Weather(unit=Unit.CELSIUS)
  1. 这实例化了一个名为weatherweather对象。现在,让我们使用这个对象。输入以下内容并按Enter
lookup = weather.lookup(4118)
  1. 我们现在有一个名为lookup的变量,它是用代码4118创建的,对应于加拿大多伦多市。输入以下内容并按Enter
condition = lookup.condition
  1. 我们现在有一个名为condition的变量,它包含了通过lookup变量获取的多伦多市的当前天气信息。要查看这些信息,输入以下内容并按Enter
print(condition.text)
  1. 你应该得到多伦多市的天气状况描述。当我运行时,返回了以下内容:
Partly Cloudy

现在我们已经看到,在树莓派上编写 Python 代码与在其他操作系统上编写一样简单,让我们再进一步编写一个简单的程序。我们将使用 Thonny 来完成这个任务。

Python 模块是一个包含可供导入使用的代码的单个 Python 文件。Python 包是一组 Python 模块。

编写一个简单的 Python 程序

我们将编写一个简单的 Python 程序,其中包含一个类。为此,我们将使用 Thonny,这是一个预先安装在 Raspbian 上并具有出色的调试和变量内省功能的 Python IDE。你会发现它的易用性使其成为我们项目开发的理想选择。

创建类

我们将从创建一个类开始我们的程序。类可以被看作是创建对象的模板。一个类包含方法和变量。要在 Thonny 中创建一个 Python 类,做如下操作:

  1. 通过应用菜单 | 编程 | Thonny 加载 Thonny。从左上角选择新建并输入以下代码:
class CurrentWeather:
    weather_data={'Toronto':['13','partly sunny','8 km/h NW'], 'Montreal':['16','mostly sunny','22 km/h W'],
                'Vancouver':['18','thunder showers','10 km/h NE'],
                'New York':['17','mostly cloudy','5 km/h SE'],
                'Los Angeles':['28','sunny','4 km/h SW'],
                'London':['12','mostly cloudy','8 km/h NW'],
                'Mumbai':['33','humid and foggy','2 km/h S']
                 }

     def __init__(self, city):
         self.city = city 

     def getTemperature(self):
         return self.weather_data[self.city][0]

     def getWeatherConditions(self):
         return self.weather_data[self.city][1]

     def getWindSpeed(self):
         return self.weather_data[self.city][2]

正如您所看到的,我们创建了一个名为CurrentWeather的类,它将保存我们为其实例化类的任何城市的天气条件。我们使用类是因为它将允许我们保持我们的代码清晰,并为以后使用外部类做好准备。

创建对象

我们现在将从我们的CurrentWeather类创建一个对象。我们将使用London作为我们的城市:

  1. 单击顶部菜单中的“运行当前脚本”按钮(一个带有白色箭头的绿色圆圈)将我们的代码加载到 Python 解释器中。

  2. 在 Thonny shell 的命令行上,输入以下内容并按Enter键:

londonWeather = CurrentWeather('London')

我们刚刚在我们的代码中创建了一个名为londonWeather的对象,来自我们的CurrentWeather类。通过将'London'传递给构造函数(init),我们将我们的新对象设置为仅发送London城市的天气信息。这是通过类属性cityself.city)完成的。

  1. 在 shell 命令行上输入以下内容:
weatherLondon.getTemperature()

您应该在下一行得到答案'12'

  1. 要查看London的天气条件,请输入以下内容:
weatherLondon.getWeatherConditions()

您应该在下一行看到“'大部分多云'”。

  1. 要获取风速,请输入以下内容并按Enter键:
weatherLondon.getWindSpeed()

您应该在下一行得到8 km/h NW

我们的CurrentWeather类模拟了来自天气数据的网络服务的数据。我们类中的实际数据存储在weather_data变量中。

在以后的代码中,尽可能地将对网络服务的调用封装在类中,以便保持组织和使代码更易读。

使用对象检查器

让我们对我们的代码进行一些分析:

  1. 从“视图”菜单中,选择“对象检查器”和“变量”。您应该看到以下内容:

  1. 在“变量”选项卡下突出显示londonWeather变量。我们可以看到londonWeatherCurrentWeather类型的对象。在对象检查器中,我们还可以看到属性city设置为'London'。这种类型的变量检查在故障排除代码中非常宝贵。

测试您的类

在编写代码时测试代码非常重要,这样您就可以尽早地捕获错误:

  1. 将以下函数添加到CurrentWeather类中:
 def getCity(self):
     return self.city
  1. 将以下内容添加到CurrentWeather.py的底部。第一行应该与类定义具有相同的缩进,因为此函数不是类的一部分:
if __name__ == "__main__":
    currentWeather = CurrentWeather('Toronto')
    wind_dir_str_len = 2

    if currentWeather.getWindSpeed()[-2:-1] == ' ':
        wind_dir_str_len = 1

     print("The current temperature in",
            currentWeather.getCity(),"is",
            currentWeather.getTemperature(),
            "degrees Celsius,",
            "the weather conditions are",
            currentWeather.getWeatherConditions(),
            "and the wind is coming out of the",
            currentWeather.getWindSpeed()[-(wind_dir_str_len):],
            "direction with a speed of",
            currentWeather.getWindSpeed()
            [0:len(currentWeather.getWindSpeed())
            -(wind_dir_str_len)]
            )
  1. 通过单击“运行当前脚本”按钮来运行代码。您应该看到以下内容:
The current temperature in Toronto is 13 degrees Celsius, the weather conditions are partly sunny and the wind is coming out of the NW direction with a speed of 8 km/h 

if __name__ == "__main__":函数允许我们直接在文件中测试类,因为if语句只有在直接运行文件时才为真。换句话说,对CurrentWeather.py的导入不会执行if语句后面的代码。随着我们逐步阅读本书,我们将更多地探索这种方法。

使代码灵活

更通用的代码更灵活。以下是我们可以使代码更少具体的两个例子。

例一

wind_dir_str_len变量用于确定风向字符串的长度。例如,S方向只使用一个字符,而 NW 则使用两个。这样做是为了在方向仅由一个字符表示时,不包括额外的空格在我们的输出中:

wind_dir_str_len = 2
if currentWeather.getWindSpeed()[-2:-1] == ' ':
    wind_dir_str_len = 1

通过使用[-2:-1]来寻找空格,我们可以确定这个字符串的长度,并在有空格时将其更改为1(因为我们从字符串的末尾返回两个字符)。

例二

通过向我们的类添加getCity方法,我们能够创建更通用名称的类,如currentWeather,而不是torontoWeather。这使得我们可以轻松地重用我们的代码。我们可以通过更改以下行来演示这一点:

currentWeather = CurrentWeather('Toronto') 

我们将其更改为:

currentWeather = CurrentWeather('Mumbai')

如果我们再次单击“运行”按钮运行代码,我们将得到句子中所有条件的不同值:

The current temperature in Mumbai is 33 degrees Celsius, the weather conditions are humid and foggy and the wind is coming out of the S direction with a speed of 2 km/h 

总结

我们开始本章时讨论了 Raspbian 中可用的各种 Python 开发工具。在终端窗口中运行 Python 的最快最简单的方法。由于 Python 预先安装在 Raspbian 中,因此在终端提示符中使用python命令加载 Python(在本例中为 Python 2)。无需设置环境变量即可使命令找到程序。通过输入python3在终端中运行 Python 3。

我们还简要介绍了 IDLE,这是 Python 开发的默认 IDE。IDLE 代表集成开发和学习环境,是初学者学习 Python 时使用的绝佳工具。

Thonny 是另一个预先安装在 Raspbian 上的 Python IDE。Thonny 具有出色的调试和变量内省功能。它也是为初学者设计的 Python 开发工具,但是其易用性和对象检查器使其成为我们项目开发的理想选择。随着我们在书中的进展,我们将更多地使用 Thonny。

然后,我们立即开始编程,以激发我们的开发热情。我们从使用终端进行简单表达式开始,并以天气数据示例结束,该示例旨在模拟用于调用 Web 服务的对象。

在第三章中,使用 GPIO 连接到外部世界,我们将立即进入树莓派上编程最强大的功能,即 GPIO。 GPIO 允许我们通过连接到树莓派上的此端口的设备与现实世界进行交互。 GPIO 编程将使我们的 Python 技能提升到一个全新的水平。

问题

  1. Thonny 适用于哪些操作系统?

  2. 我们如何从终端命令行进入 Python 2?

  3. Thonny 中的哪个工具用于查看对象内部的内容?

  4. 给出两个原因,说明为什么我们在天气示例代码中使用对象。

  5. CurrentWeather类添加一个名为getCity的方法的优点是什么?

  6. IDLE 是用哪种语言编写的?

  7. 为了打印当前日期和时间,需要采取哪两个步骤?

  8. 在我们的代码中,我们是如何补偿只用一个字母表示的风速方向的?

  9. if __name__ =="__main__"语句的作用是什么?

  10. IDLE 代表什么?

进一步阅读

Dusty PhillipsPython 3 - 面向对象编程,Packt Publishing。

第三章:使用 GPIO 连接到外部世界

在本章中,我们将开始解锁树莓派背后真正的力量——GPIO,或通用输入输出。 GPIO 允许您通过可以设置为输入或输出的引脚将树莓派连接到外部世界,并通过代码进行控制。

本章将涵盖以下主题:

  • 树莓派的 Python 库

  • 访问树莓派的 GPIO

  • 设置电路

  • 你好 LED

项目概述

在本章中,我们首先探索了 Python 的树莓派特定库。我们将使用树莓派相机模块和 Pibrella HAT 的几个示例来演示这些内容。在转到使用 Fritzing 程序设计物理电路之前,我们将尝试使用 Sense Hat 模拟器进行一些编码示例。使用面包板,我们将设置这个电路并将其连接到我们的树莓派。

我们将通过在第二章中创建的类中构建一个摩尔斯电码生成器,该生成器将以摩尔斯电码传输天气数据来结束本章,使用树莓派编写 Python 程序。完成本章应该需要一个下午的时间。

技术要求

完成此项目需要以下内容:

树莓派的 Python 库

我们将把注意力转向 Raspbian 预装的 Python 库或包。要从 Thonny 查看这些包,请单击工具|管理包。稍等片刻后,您应该会在对话框中看到许多列出的包:

让我们来探索其中一些包。

picamera

树莓派上的相机端口或 CSI 允许您将专门设计的树莓派相机模块连接到您的 Pi。该相机可以拍摄照片和视频,并具有进行延时摄影和慢动作视频录制的功能。picamera包通过 Python 使我们可以访问相机。以下是连接到树莓派 3 Model B 的树莓派相机模块的图片:

将树莓派相机模块连接到您的 Pi,打开 Thonny,并输入以下代码:

import picamera
import time

picam = picamera.PiCamera()
picam.start_preview()
time.sleep(10)
picam.stop_preview()
picam.close()

此代码导入了picameratime包,然后创建了一个名为picampicamera对象。从那里,我们开始预览,然后睡眠10秒,然后停止预览并关闭相机。运行程序后,您应该在屏幕上看到来自相机的10秒预览。

枕头

Pillow 包用于 Python 图像处理。要测试这一点,请将图像下载到与项目文件相同的目录中。在 Thonny 中创建一个新文件,然后输入以下内容:

from PIL import Image

img = Image.open('image.png')
print(img.format, img.size)

您应该在随后的命令行中看到图像的格式和大小(括号内)打印出来。

sense-hat 和 sense-emu

Sense HAT 是树莓派的一个复杂的附加板。Sense HAT 是 Astro Pi 套件的主要组件,是一个让年轻学生为国际空间站编程树莓派的计划的一部分。

Astro Pi 比赛于 2015 年 1 月正式向英国所有小学和中学年龄的孩子开放。在对国际空间站的任务中,英国宇航员蒂姆·皮克在航天站上部署了 Astro Pi 计算机。

获胜的 Astro Pi 比赛代码被加载到太空中的 Astro Pi 上。生成的数据被收集并发送回地球。

Sense HAT 包含一组 LED,可用作显示器。Sense HAT 还具有以下传感器:

  • 加速度计

  • 温度传感器

  • 磁力计

  • 气压传感器

  • 湿度传感器

  • 陀螺仪

我们可以通过sense-hat包访问 Sense HAT 上的传感器和 LED。对于那些没有 Sense HAT 的人,可以使用 Raspbian 中的 Sense HAT 模拟器。我们使用sense-emu包来访问 Sense HAT 模拟器上模拟的传感器和 LED 显示。

为了演示这一点,请执行以下步骤:

  1. 在 Thonny 中创建一个新文件,并将其命名为sense-hat-test.py,或类似的名称。

  2. 键入以下代码:

from sense_emu import SenseHat

sense_emulator = SenseHat()
sense_emulator.show_message('Hello World')
  1. 从应用程序菜单|编程|Sense HAT 模拟器加载 Sense HAT 模拟器程序。

  2. 调整屏幕,以便您可以看到 Sense HAT 模拟器的 LED 显示和 Thonny 的完整窗口(请参见下一张截图):

  1. 单击运行当前脚本按钮。

  2. 你应该看到“Hello World!”消息一次一个字母地滚动在 Sense HAT 模拟器的 LED 显示器上(请参见上一张截图)。

访问树莓派的 GPIO

通过 GPIO,我们能够连接到外部世界。以下是树莓派 GPIO 引脚的图示:

以下是这些引脚的解释:

  • 红色引脚代表 GPIO 输出的电源。GPIO 提供 3.3 伏特和 5 伏特。

  • 黑色引脚代表用于电气接地的引脚。正如您所看到的,GPIO 上有 8 个接地引脚。

  • 蓝色引脚用于树莓派的硬件附加在顶部HATs)。它们允许树莓派和 HAT 的电可擦可编程只读存储器EEPROM)之间的通信。

  • 绿色引脚代表我们可以为其编程的输入和输出引脚。请注意,一些绿色 GPIO 引脚具有额外的功能。我们将不会涵盖这个项目的额外功能。

GPIO 是树莓派的核心。我们可以通过 GPIO 将 LED、按钮、蜂鸣器等连接到树莓派上。我们还可以通过为树莓派设计的 HAT 来访问 GPIO。其中之一叫做Pibrella,这是我们接下来将使用的,用来通过 Python 代码探索连接到 GPIO。

树莓派 1 型 A 和 B 型只有前 26 个引脚(如虚线所示)。从那时起的型号,包括树莓派 1 型 A+和 B+,树莓派 2,树莓派 Zero 和 Zero W,以及树莓派 3 型 B 和 B+,都有 40 个 GPIO 引脚。

Pibrella

Pibrella 是一个相对便宜的树莓派 HAT,可以轻松连接到 GPIO。以下是 Pibrella 板上的组件:

  • 1 个红色 LED

  • 1 个黄色 LED

  • 1 个绿色 LED

  • 小音箱

  • 按键

  • 4 个输入

  • 4 个输出

  • Micro USB 电源连接器,用于向输出提供更多电源

Pibrella 是为早期的树莓派型号设计的,因此只有 26 个引脚输入。但是,它可以通过前 26 个引脚连接到后来的型号。

要安装 Pibrella Hat,将 Pibrella 上的引脚连接器与树莓派上的前 26 个引脚对齐,并向下按。在下图中,我们正在将 Pibrella 安装在树莓派 3 型 B 上:

安装 Pibrella 时应该很合适:

连接到 Pibrella 所需的库在 Raspbian 中没有预先安装(截至撰写本文的时间),因此我们必须自己安装它们。为此,我们将使用终端中的pip3命令:

  1. 通过单击顶部工具栏上的终端(从左起的第四个图标)加载终端。在命令提示符下,键入以下内容:
sudo pip3 install pibrella
  1. 您应该看到终端加载软件包:

  1. 使用Pibrella库,无需知道 GPIO 引脚编号即可访问 GPIO。该功能被包装在我们导入到代码中的Pibrella对象中。我们将进行一个简短的演示。

  2. 在 Thonny 中创建一个名为pibrella-test.py的新文件,或者取一个类似的名字。键入以下代码:

import pibrella
import time

pibrella.light.red.on()
time.sleep(5)
pibrella.light.red.off()
pibrella.buzzer.success()
  1. 点击运行当前脚本按钮运行代码。如果您输入的一切都正确,您应该看到 Pibrella 板上的红灯在5秒钟内亮起,然后扬声器发出短暂的旋律。

恭喜,您现在已经跨越了物理计算的门槛。

RPi.GPIO

用于访问 GPIO 的标准 Python 包称为RPi.GPIO。描述它的最佳方式是使用一些代码(这仅用于演示目的;我们将在接下来的部分中运行代码来访问 GPIO):

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
GPIO.output(18, GPIO.HIGH)
time.sleep(5)
GPIO.output(18, GPIO.LOW)

正如您所看到的,这段代码似乎有点混乱。我们将逐步介绍它:

  1. 首先,我们导入RPi.GPIOtime库:
import RPi.GPIO as GPIO
import time
  1. 然后,我们将模式设置为BCM
GPIO.setmode(GPIO.BCM)
  1. 在 BCM 模式下,我们通过 GPIO 编号(显示在我们的树莓派 GPIO 图形中的编号)访问引脚。另一种方法是通过它们的物理位置(GPIO.BOARD)访问引脚。

  2. 要将 GPIO 引脚18设置为输出,我们使用以下行:

GPIO.setup(18, GPIO.OUT)
  1. 然后我们将 GPIO 18设置为HIGH,持续5秒,然后将其设置为LOW
GPIO.output(18, GPIO.HIGH)
time.sleep(5)
GPIO.output(18, GPIO.LOW)

如果我们设置了电路并运行了代码,我们会看到 LED 在5秒钟内亮起,然后关闭,类似于 Pibrella 示例。

GPIO 零

RPi.GPIO的替代方案是 GPIO Zero 包。与RPi.GPIO一样,这个包已经预装在 Raspbian 中。名称中的零指的是零样板或设置代码(我们被迫每次输入的代码)。

为了完成打开和关闭 LED 灯 5 秒钟的相同任务,我们使用以下代码:

from gipozero import LED
import time

led = LED(18)
led.on()
time.sleep(5)
led.off()

与我们的RPi.GPIO示例一样,这段代码仅用于演示目的,因为我们还没有设置电路。很明显,GPIO Zero 代码比RPi.GPIO示例简单得多。这段代码非常容易理解。

在接下来的几节中,我们将在面包板上构建一个物理电路,其中包括 LED,并使用我们的代码来打开和关闭它。

设置电路

Pibrella HAT 为我们提供了一种简单的编程 GPIO 的方法,然而,树莓派项目的最终目标是创建一个定制的工作电路。我们现在将采取步骤设计我们的电路,然后使用面包板创建电路。

第一步是在计算机上设计我们的电路。

Fritzing

Fritzing 是一款免费的电路设计软件,适用于 Windows、macOS 和 Linux。树莓派商店中有一个版本,我们将在树莓派上安装它:

  1. 从应用菜单中,选择首选项|添加/删除软件。在搜索框中,键入Fritzing

  1. 选择所有三个框,然后单击应用,然后单击确定。安装后,您应该能够从应用菜单|编程|Fritzing 中加载 Fritzing。

  2. 点击面包板选项卡以访问面包板设计屏幕。一个全尺寸的面包板占据了屏幕的中间。我们将它缩小,因为我们的电路很小而简单。

  3. 点击面包板。在检查器框中,您会看到一个名为属性的标题。

  4. 点击大小下拉菜单,选择 Mini。

  5. 要将树莓派添加到我们的电路中,在搜索框中键入Raspberry Pi。将树莓派 3 拖到我们的面包板下方。

  6. 从这里,我们可以将组件拖放到面包板上。

  7. 将 LED 和 330 欧姆电阻器添加到我们的面包板上,如下图所示。我们使用电阻器来保护 LED 和树莓派免受可能造成损坏的过大电流:

  1. 当我们将鼠标悬停在树莓派组件的每个引脚上时,会弹出一个黄色提示,显示引脚的 BCM 名称。点击 GPIO 18,将线拖到 LED 的正极(较长的引脚)。

  2. 同样,将 GND 连接拖到电阻的左侧。

这是我们将为树莓派构建的电路。

构建我们的电路

要构建我们的物理电路,首先要将组件插入我们的面包板。参考之前的图表,我们可以看到一些孔是绿色的。这表示电路中有连续性。例如,我们通过同一垂直列将 LED 的负极连接到 330 欧姆电阻。因此,两个组件的引脚通过面包板连接在一起。

在我们开始在面包板上放置组件时,我们要考虑这一点:

  1. 将 LED 插入我们的面包板,如上图所示。我们遵循我们的 Fritzing 图表,并将正极插入下方的孔中。

  2. 按照我们的 Fritzing 图表,连接 330 欧姆电阻。使用母对公跳线,将树莓派连接到面包板上。

  3. 参考我们的树莓派 GPIO 图表,在树莓派主板上找到 GPIO 18 和 GND。

在连接跳线到 GPIO 时,最好将树莓派断电。

如下图所示,完整的电路类似于我们的 Fritzing 图表(只是我们的面包板和树莓派被转向):

  1. 将树莓派重新连接到显示器、电源、键盘和鼠标。

我们现在准备好编程我们的第一个真正的 GPIO 电路。

Hello LED

我们将直接进入代码:

  1. 在 Thonny 中创建一个新文件,并将其命名为Hello LED.py或类似的名称。

  2. 输入以下代码并运行:

from gpiozero import LED

led = LED(18)
led.blink(1,1,10)

使用 gpiozero 闪烁 LED

如果我们正确连接了电路并输入了正确的代码,我们应该看到 LED 以 1 秒的间隔闪烁 10 秒。gpiozero LED对象中的blink函数允许我们设置on_time(LED 保持打开的时间长度,以秒为单位)、off_time(LED 关闭的时间长度,以秒为单位)、n或 LED 闪烁的次数,以及background(设置为True以允许 LED 闪烁时运行其他代码)。

带有默认参数的blink函数调用如下:

blink(on_time=1, off_time=1, n=none, background=True)

在函数中不传递参数时,LED 将以 1 秒的间隔不停地闪烁。请注意,我们不需要像使用RPi.GPIO包访问 GPIO 时那样导入time库。我们只需将一个数字传递给blink函数,表示我们希望 LED 打开或关闭的时间(以秒为单位)。

摩尔斯码天气数据

在第二章中,使用树莓派编写 Python 程序,我们编写了模拟调用提供天气信息的网络服务的代码。根据本章学到的知识,让我们重新审视该代码,并对其进行物理计算升级。我们将使用 LED 来闪烁表示我们的天气数据的摩尔斯码。

我们中的许多人认为,世界直到 1990 年代才开始通过万维网变得连接起来。我们很少意识到,19 世纪引入电报和跨世界电报电缆时,我们已经有了这样一个世界。这个所谓的维多利亚时代互联网的语言是摩尔斯码,摩尔斯码操作员是它的门卫。

以下是闪烁摩尔斯码表示我们的天气数据的步骤:

  1. 我们首先将创建一个MorseCodeGenerator类:
from gpiozero import LED
from time import sleep

class MorseCodeGenerator:

    led = LED(18)
    dot_duration = 0.3
    dash_duration = dot_duration * 3
    word_spacing_duration = dot_duration * 7

    MORSE_CODE = {
        'A': '.-', 'B': '-...', 'C': '-.-.', 
        'D': '-..', 'E': '.', 'F': '..-.',
        'G': '--.', 'H': '....', 'I': '..',
        'J': '.---', 'K': '-.-', 'L': '.-..',
        'M': '--', 'N': '-.', 'O': '---',
        'P': '.--.', 'Q': '--.-', 'R': '.-.',
       'S': '...', 'T': '-', 'U': '..-',
        'V': '...-', 'W': '.--', 'X': '-..-',
        'Y': '-.--', 'Z': '--..', '0': '-----',
        '1': '.----', '2': '..---', '3': '...--',
        '4': '....-', '5': '.....', '6': '-....',
        '7': '--...', '8': '---..', '9': '----.',
        ' ': ' '
        } 

    def transmit_message(self, message):
        for letter in message: 
            morse_code_letter = self.MORSE_CODE[letter.upper()]

            for dash_dot in morse_code_letter:

                if dash_dot == '.':
                    self.dot()

                elif dash_dot == '-':
                    self.dash()

                elif dash_dot == ' ':
                    self.word_spacing()

            self.letter_spacing()

    def dot(self):
        self.led.blink(self.dot_duration,self.dot_duration,1,False)

    def dash(self):
        self.led.blink(self.dash_duration,self.dot_duration,1,False)

    def letter_spacing(self):
        sleep(self.dot_duration)

    def word_spacing(self):
        sleep(self.word_spacing_duration-self.dot_duration)

if __name__ == "__main__":

    morse_code_generator = MorseCodeGenerator()
    morse_code_generator.transmit_message('SOS')    
  1. 在我们的MorseCodeGenerator类中导入gpiozerotime库后,我们将 GPIO 18 定义为我们的 LED,代码为led=LED(18)

  2. 我们使用dot_duration = 0.3来设置dot持续的时间。

  3. 然后我们根据dot_duration定义破折号的持续时间和单词之间的间距。

  4. 为了加快或减慢我们的莫尔斯码转换,我们可以相应地调整dot_duration

  5. 我们使用一个名为MORSE_CODE的 Python 字典。我们使用这个字典将字母转换为莫尔斯码。

  6. 我们的transmit_message函数逐个遍历消息中的每个字母,然后遍历莫尔斯码中的每个字符,这相当于使用dash_dot变量。

  7. 我们的类的魔力在dotdash方法中发生,它们使用了gpiozero库中的blink函数:

def dot(self):
       self.led.blink(self.dot_duration, self.dot_duration,1,False)

dot方法中,我们可以看到我们将 LED 打开的持续时间设置为dot_duration,然后我们将其关闭相同的时间。我们只闪烁一次,因为在blink方法调用中将其设置为数字1。我们还将背景参数设置为False

这个最后的参数非常重要,因为如果我们将其保留为默认值True,那么 LED 在有机会闪烁之前,代码将继续运行。基本上,除非将背景参数设置为False,否则代码将无法工作。

在我们的测试消息中,我们放弃了通常的Hello World,而是使用了标准的SOS,这对于大多数莫尔斯码爱好者来说是熟悉的。我们可以通过单击“运行”按钮来测试我们的类,如果一切设置正确,我们将看到 LED 以莫尔斯码闪烁 SOS。

现在,让我们重新审视一下第二章中的CurrentWeather类,即使用树莓派编写 Python 程序。我们将进行一些小的修改:

from MorseCodeGenerator import MorseCodeGenerator

class CurrentWeather:

    weather_data={
        'Toronto':['13','partly sunny','8 NW'],
        'Montreal':['16','mostly sunny','22 W'],
        'Vancouver':['18','thunder showers','10 NE'],
        'New York':['17','mostly cloudy','5 SE'],
        'Los Angeles':['28','sunny','4 SW'],
        'London':['12','mostly cloudy','8 NW'],
        'Mumbai':['33','humid and foggy','2 S']
    }

    def __init__(self, city):
        self.city = city 

    def getTemperature(self):
        return self.weather_data[self.city][0]

    def getWeatherConditions(self):
        return self.weather_data[self.city][1]

    def getWindSpeed(self):
        return self.weather_data[self.city][2]

    def getCity(self):
        return self.city

if __name__ == "__main__":

    current_weather = CurrentWeather('Toronto')
    morse_code_generator = MorseCodeGenerator()
    morse_code_generator.transmit_message(current_weather.
    getWeatherConditions())

我们首先导入我们的MorseCodeGenerator类(确保两个文件在同一个目录中)。由于我们没有/的莫尔斯码等价物,我们从weather_data数据集中去掉了 km/h。类的其余部分与第二章中的内容保持一致,即使用树莓派编写 Python 程序。在我们的测试部分,我们实例化了CurrentWeather类和MorseCodeGenerator类。使用CurrentWeather类,我们将多伦多的天气条件传递给MorseCodeGenerator类。

如果在输入代码时没有出现任何错误,我们应该能够看到 LED 以莫尔斯码闪烁“部分晴天”。

摘要

本章涵盖了很多内容。到最后,您应该对在树莓派上开发应用程序感到非常满意。

picameraPillowsense-hat库使得使用树莓派与外部世界进行通信变得很容易。使用树莓派摄像头模块和picamera,我们为树莓派打开了全新的可能性。我们只是触及了picamera的一小部分功能。此外,我们只是浅尝了Pillow库的图像处理功能。Sense HAT 模拟器使我们可以节省购买实际 HAT 的费用,并测试我们的代码。通过sense-hat和树莓派 Sense HAT,我们真正扩展了我们在物理世界中的影响力。

廉价的 Pibrella HAT 提供了一个简单的方式来进入物理计算世界。通过安装pibrella库,我们让我们的 Python 代码可以访问一系列 LED、扬声器和按钮,它们都被整齐地打包在一个树莓派 HAT 中。

然而,物理计算的真正终极目标是构建电子电路,以弥合我们的树莓派和外部世界之间的差距。我们开始使用树莓派商店提供的 Fritzing 电路构建器来构建电子电路。然后,我们在面包板上用 LED 和电阻器构建了我们的第一个电路。

我们通过使用树莓派和 LED 电路创建了一个莫尔斯码生成器来结束本章。在新旧结合的转折中,我们能够通过闪烁 LED 以莫尔斯码传输天气数据。

在[第四章](626664bb-0130-46d1-b431-682994472fc1.xhtml)中,订阅 Web 服务,我们将把 Web 服务纳入我们的代码中,从而将互联网世界与现实世界连接起来,形成一个称为物联网的概念。

问题

  1. Python 包的名称是什么,可以让您访问树莓派相机模块?

  2. 真或假?由学生编写的树莓派已部署在国际空间站上。

  3. Sense HAT 包含哪些传感器?

  4. 真或假?我们不需要为开发购买树莓派 Sense HAT,因为 Raspbian 中存在这个 HAT 的模拟器。

  5. GPIO 上有多少个接地引脚?

  6. 真或假?树莓派的 GPIO 引脚提供 5V 和 3.3V。

  7. Pibrella 是什么?

  8. 真或假?只能在早期的树莓派计算机上使用 Pibrella。

  9. BCM 模式是什么意思?

  10. 真或假?BOARD 是 BCM 的替代品。

  11. gpiozero中的 Zero 指的是什么?

  12. 真或假?使用 Fritzing,我们可以为树莓派设计一个 GPIO 电路。

  13. gpiozero LED blink函数中的默认背景参数设置为什么?

  14. 真或假?使用gpiozero库访问 GPIO 比使用RPi.GPIO库更容易。

  15. 什么是维多利亚时代的互联网?

进一步阅读

本章涵盖了许多概念,假设所需的技能不超出普通开发人员和修补者的能力。为了进一步巩固对这些概念的理解,请谷歌以下内容:

  • 如何安装树莓派相机模块

  • 如何使用面包板?

  • Fritzing 电路设计软件简介

  • Python 字典

对于那些像我一样对过去的技术着迷的人,以下是一本关于维多利亚时代互联网的好书:维多利亚时代的互联网,作者汤姆·斯坦德奇。

第四章:订阅 Web 服务

我们许多人都认为互联网建立在其上的技术是理所当然的。当我们访问我们喜爱的网站时,我们很少关心我们正在查看的网页是为我们的眼睛而制作的。然而,在底层是通信协议的互联网协议套件。机器也可以利用这些协议,通过 Web 服务进行机器之间的通信。

在本章中,我们将继续我们的连接设备通过物联网IoT)的旅程。我们将探索 Web 服务和它们背后的各种技术。我们将以一些 Python 代码结束我们的章节,其中我们调用一个实时天气服务并提取信息。

本章将涵盖以下主题:

  • 物联网的云服务

  • 编写 Python 程序提取实时天气数据

先决条件

读者应该具有 Python 编程语言的工作知识,以完成本章,以及对基本面向对象编程的理解。这将为读者服务良好,因为我们将把我们的代码分成对象。

项目概述

在这个项目中,我们将探索各种可用的 Web 服务,并涉及它们的核心优势。然后,我们将编写调用 Yahoo! Weather Web 服务的代码。最后,我们将使用树莓派 Sense HAT 模拟器显示实时天气数据的“滚动”显示。

本章应该需要一个上午或下午来完成。

入门

要完成这个项目,需要以下内容:

  • 树莓派 3 型号(2015 年或更新型号)

  • USB 电源适配器

  • 计算机显示器(支持 HDMI)

  • USB 键盘

  • USB 鼠标

  • 互联网接入

物联网的云服务

有许多云服务可供我们用于物联网开发。一些科技界最大的公司已经全力支持物联网,特别是具有人工智能的物联网。

以下是一些这些服务的详细信息。

亚马逊网络服务 IoT

亚马逊网络服务 IoT 是一个云平台,允许连接设备与其他设备或云应用程序安全地交互。这些是按需付费的服务,无需服务器,从而简化了部署和可扩展性。

亚马逊网络服务AWS)的服务,AWS IoT 核心可以使用如下:

  • AWS Lambda

  • 亚马逊 Kinesis

  • 亚马逊 S3

  • 亚马逊机器学习

  • 亚马逊 DynamoDB

  • 亚马逊 CloudWatch

  • AWS CloudTrail

  • 亚马逊 Elasticsearch 服务

AWS IoT 核心应用程序允许收集、处理和分析由连接设备生成的数据,无需管理基础设施。定价是按发送和接收的消息计费。

以下是 AWS IoT 的使用示意图。在这种情况下,汽车的道路状况数据被发送到云端并存储在 S3 云存储服务中。AWS 服务将这些数据广播给其他汽车,警告它们可能存在危险的道路状况:

IBM Watson 平台

IBM Watson 是一个能够用自然语言回答问题的系统。最初设计用来参加电视游戏节目Jeopardy!,Watson 以 IBM 的第一任 CEO Thomas J. Watson 的名字命名。2011 年,Watson 挑战了Jeopardy!冠军 Brad Rutter 和 Ken Jennings 并获胜。

使用 IBM Watson 开发者云的应用程序可以通过 API 调用来创建。使用 Watson 处理物联网信息的潜力是巨大的。

直白地说,Watson 是 IBM 的一台超级计算机,可以通过 API 调用在网上访问。

Watson 与 IoT 的一个应用是 IBM Watson 助手汽车版,这是为汽车制造商提供的集成解决方案。通过这项技术,驾驶员和乘客可以与外界互动,例如预订餐厅和检查日历中的约会。车辆中的传感器可以集成,向 IBM Watson 助手提供车辆状态的信息,如轮胎压力。以下是一个图表,说明了 Watson 如何警告驾驶员轮胎压力过低,建议修理,并预约车库:

IBM Watson 助手汽车版作为白标服务出售,以便制造商可以将其标记为适合自己的需求。IBM Watson 助手汽车版的成功将取决于它与亚马逊的 Alexa 和谷歌的 AI 助手等其他 AI 助手服务的竞争情况。与 Spotify 音乐和亚马逊购物等热门服务的整合也将在未来的成功中发挥作用。

谷歌云平台

虽然谷歌的 IoT 并不像 AWS IoT 那样广泛和有文档记录,但谷歌对 IoT 的兴趣很大。开发人员可以通过使用谷歌云服务来利用谷歌的处理、分析和机器智能技术。

以下是谷歌云服务提供的一些服务列表:

  • App Engine:应用程序托管服务

  • BigQuery:大规模数据库分析服务

  • Bigtable:可扩展的数据库服务

  • Cloud AutoML:允许开发人员访问谷歌神经架构搜索技术的机器学习服务

  • 云机器学习引擎:用于 TensorFlow 模型的机器学习服务

  • 谷歌视频智能:分析视频并创建元数据的服务

  • 云视觉 API:通过机器学习返回图像数据的服务

以下是谷歌云视觉 API 的使用图表。一张狗站在一个颠倒的花盆旁边的图片通过 API 传递给服务。图像被扫描,并且使用机器学习在照片中识别物体。返回的 JSON 文件包含结果的百分比:

谷歌专注于使事情简单快捷,使开发人员可以访问谷歌自己的全球私人网络。谷歌云平台的定价低于 AWS IoT。

微软 Azure

微软 Azure(以前称为 Windows Azure)是微软的基于云的服务,允许开发人员使用微软庞大的数据中心构建、测试、部署和管理应用程序。它支持许多不同的编程语言,既是微软特有的,也来自外部第三方。

Azure Sphere 是微软 Azure 框架的一部分,于 2018 年 4 月推出,是 Azure 的 IoT 解决方案。以下是 Azure Sphere(或如图表所示的 Azure IoT)可能被使用的场景。在这种情况下,远程工厂中的机器人手臂通过手机应用程序进行监控和控制:

您可能已经注意到,前面的例子可以使用任何竞争对手的云服务来设置,这确实是重点。通过相互竞争,服务变得更好、更便宜,因此更易获得。

随着 IBM、亚马逊、谷歌和微软等大公司参与 IoT 数据处理,IoT 的未来是无限的。

Weather Underground

虽然不像谷歌和 IBM 那样重量级,Weather Underground 提供了一个天气信息的网络服务,开发人员可以将他们的应用程序与之联系起来。通过使用开发人员账户,可以构建利用当前天气条件的 IoT 应用程序。

在撰写本章时,Weather Underground 网络为开发人员提供了 API 以访问天气信息。自那时起,Weather Underground API 网站发布了服务终止通知。要了解此服务的状态,请访问www.wunderground.com/weather/api/

从云中提取数据的基本 Python 程序

在第二章中,使用树莓派编写 Python 程序,我们介绍了一个名为weather-api的包,它允许我们访问 Yahoo! Weather Web 服务。在本节中,我们将在我们自己的类中包装weather-api包中的Weather对象。我们将重用我们的类名称CurrentWeather。在测试我们的CurrentWeather类之后,我们将在 Raspbian 中利用 Sense Hat 模拟器并构建一个天气信息滚动条。

访问 Web 服务

我们将首先修改我们的CurrentWeather类,以通过weather-api包对 Yahoo! Weather 进行 Web 服务调用:

  1. 从应用程序菜单|编程|Thonny Python IDE 打开 Thonny。

  2. 单击新图标创建新文件。

  3. 输入以下内容:

from weather import Weather, Unit

class CurrentWeather:
     temperature = ''
     weather_conditions = ''
     wind_speed = ''
     city = ''

     def __init__(self, city):
         self.city = city
         weather = Weather(unit = Unit.CELSIUS)
         lookup = weather.lookup_by_location(self.city)
         self.temperature = lookup.condition.temp
         self.weather_conditions = lookup.condition.text
         self.wind_speed = lookup.wind.speed

     def getTemperature(self):
         return self.temperature

     def getWeatherConditions(self):
         return self.weather_conditions

     def getWindSpeed(self):
         return self.wind_speed

     def getCity(self):
         return self.city

if __name__=="__main__":
        current_weather = CurrentWeather('Montreal')
        print("%s %sC %s wind speed %s km/h"
        %(current_weather.getCity(),
        current_weather.getTemperature(),
        current_weather.getWeatherConditions(),
        current_weather.getWindSpeed()))
  1. 将文件保存为CurrentWeather.py

  2. 运行代码。

  3. 您应该在 Thonny 的 shell 中看到来自 Web 服务的天气信息打印出来。当我运行程序时,我看到了以下内容:

Toronto 12.0C Clear wind speed 0 km/h
  1. 现在,让我们仔细看看代码,看看发生了什么。我们首先从我们需要的程序包中导入资源:
from weather import Weather, Unit
  1. 然后我们定义我们的类名CurrentWeather,并将类变量(temperatureweather_conditionswind_speedcity)设置为初始值:
class CurrentWeather:
     temperature = ''
     weather_conditions = ''
     wind_speed = ''
     city = ''
  1. init方法中,我们根据传入方法的city设置我们的类变量。我们通过将一个名为weather的变量实例化为Weather对象,并将unit设置为CELSIUS来实现这一点。lookup变量是基于我们传入的city名称创建的。从那里,简单地设置我们的类变量(temperatureweather_conditionswind_speed)从我们从lookup中提取的值。weather-api为我们完成了所有繁重的工作,因为我们能够使用点表示法访问值。我们无需解析 XML 或 JSON 数据:
def __init__(self, city):
    self.city = city
    weather = Weather(unit = Unit.CELSIUS)
    lookup = weather.lookup_by_location(self.city)
    self.temperature = lookup.condition.temp
    self.weather_conditions = lookup.condition.text
     self.wind_speed = lookup.wind.speed
  1. init方法中设置类变量后,我们使用方法调用来返回这些类变量:
def getTemperature(self):
    return self.temperature

def getWeatherConditions(self):
    return self.weather_conditions

def getWindSpeed(self):
    return self.wind_speed

def getCity(self):
    return self.city
  1. 由于我们在 Thonny 中作为程序运行CurrentWeather.py,我们可以使用if __name__=="__main__"方法并利用CurrentWeather类。请注意,if __name__=="__main__"方法的缩进与类名相同。如果不是这样,它将无法工作。

在 Python 的每个模块中,都有一个名为__name__的属性。如果您要检查已导入到程序中的模块的此属性,您将得到返回的模块名称。例如,如果我们在前面的代码中放置行print(Weather.__name__),我们将得到名称Weather。在运行文件时检查__name__返回__main__值。

  1. if __name__=="__main__"方法中,我们创建一个名为current_weatherCurrentWeather类型的对象,传入城市名Montreal。然后,我们使用适当的方法调用打印出citytemperatureweather conditionswind speed的值:
if __name__=="__main__":
    current_weather = CurrentWeather('Montreal')
    print("%s %sC %s wind speed %s km/h"
    %(current_weather.getCity(),
    current_weather.getTemperature(),
    current_weather.getWeatherConditions(),
    current_weather.getWindSpeed()))

使用 Sense HAT 模拟器

现在,让我们使用树莓派 Sense HAT 模拟器来显示天气数据。我们将利用我们刚刚创建的CurrentWeather类。要在 Sense HAT 模拟器中看到显示的天气信息,请执行以下操作:

  1. 从应用程序菜单|编程|Thonny Python IDE 打开 Thonny

  2. 单击新图标创建新文件

  3. 输入以下内容:

from sense_emu import SenseHat
from CurrentWeather import CurrentWeather

class DisplayWeather:
    current_weather = ''

    def __init__(self, current_weather):
        self.current_weather = current_weather

    def display(self):
        sense_hat_emulator = SenseHat()

        message = ("%s %sC %s wind speed %s km/h"
           %(self.current_weather.getCity(),
           self.current_weather.getTemperature(),
           self.current_weather.getWeatherConditions(),
           self.current_weather.getWindSpeed()))

        sense_hat_emulator.show_message(message)

if __name__ == "__main__":
    current_weather = CurrentWeather('Toronto')
    display_weather = DisplayWeather(current_weather)
    display_weather.display()
  1. 将文件保存为DisplayWeather.py

  2. 从应用程序菜单|编程|Sense HAT 模拟器加载 Sense HAT 模拟器

  3. 将 Sense HAT 模拟器定位到可以看到显示的位置

  4. 运行代码

你应该在 Sense HAT 模拟器显示器上看到多伦多的天气信息滚动条,类似于以下截图:

那么,我们是如何做到这一点的呢?initmessage方法是这个程序的核心。我们通过设置类变量current_weather来初始化DisplayWeather类。一旦current_weather被设置,我们就在display方法中从中提取值,以便构建我们称之为message的消息。然后我们也在display方法中创建一个SenseHat模拟器对象,并将其命名为sense_hat_emulator。我们通过sense_hat_emulator.show_message(message)这一行将我们的消息传递给SenseHat模拟器的show_message方法:

def __init__(self, current_weather):
    self.current_weather = current_weather

def display(self):
    sense_hat_emulator = SenseHat()

    message = ("%s %sC %s wind speed %s km/h"
           %(self.current_weather.getCity(),
           self.current_weather.getTemperature(),
           self.current_weather.getWeatherConditions(),
           self.current_weather.getWindSpeed()))

    sense_hat_emulator.show_message(message)

总结

我们从讨论一些可用的各种网络服务开始了本章。我们讨论了一些在人工智能和物联网领域中最大的信息技术公司的工作。

亚马逊和谷歌都致力于成为物联网设备连接的平台。亚马逊通过其亚马逊网络服务提供了大量的文档和支持。谷歌也在建立一个强大的物联网平台。哪个平台会胜出还有待观察。

IBM 在人工智能领域的涉足集中在 Watson 上,他们的Jeopardy!游戏冠军。当然,赢得游戏秀并不是 Watson 的最终目标。然而,从这些追求中建立的知识和技术将会进入我们今天只能想象的领域。Watson 可能会被证明是物联网世界的所谓杀手应用程序。

也许没有什么比天气更多人谈论的了。在本章中,我们使用weather-api包利用内置在 Raspbian 操作系统中的树莓派 Sense HAT 模拟器构建了一个天气信息滚动条。

在第五章中,使用 Python 控制舵机,我们将探索使用舵机以提供模拟显示的其他通信方式。

问题

  1. IBM Watson 是什么?

  2. 真的还是假的?亚马逊的物联网网络服务允许访问亚马逊的其他基于云的服务。

  3. 真的还是假的?Watson 是Jeopardy!游戏秀的冠军吗?

  4. 真的还是假的?谷歌有他们自己的全球私人网络。

  5. 真的还是假的?当我们引入网络服务数据时,我们需要更改函数的名称,比如getTemperature

  6. 真的还是假的?在你的类中使用测试代码以隔离该类的功能是一个好主意。

  7. 在我们的代码中,DisplayWeather类的目的是什么?

  8. 在我们的代码中,我们使用SenseHat对象的哪种方法来在 Sense HAT 模拟器中显示天气信息?

进一步阅读

在扩展你对网络服务的知识时,通过谷歌搜索可用的各种网络服务是一个很好的起点。

第五章:使用 Python 控制舵机

在数字技术兴起之前,模拟仪表和仪器是显示数据的唯一方式。一旦转向数字技术,模拟仪表就不再流行。在模拟时钟上学习报时的一代人可能会突然发现这项技能已经过时,因为数字显示时间已经成为常态。

在本章中,我们将通过根据数字值改变舵机的位置来弥合数字世界和模拟世界之间的差距。

本章将涵盖以下主题:

  • 将舵机连接到树莓派

  • 通过命令行控制舵机

  • 编写一个 Python 程序来控制舵机

完成本章所需的知识

读者需要对 Python 编程语言有一定的了解才能完成本章。还必须了解使用简单的面包板连接组件。

项目概述

在这个项目中,我们将连接一个舵机和 LED,并使用GPIO Zero库来控制它。我们将首先在 Fritzing 中设计电路,然后进行组装。

我们将开始使用 Python shell 来控制舵机。

最后,我们将通过创建一个 Python 类来扩展这些知识,该类将根据传递给类的数字打开、关闭或闪烁 LED,并根据百分比量来转动舵机。

这个项目应该需要大约 2 个小时来完成。

入门

完成这个项目需要以下物品:

  • 树莓派 3 型号(2015 年或更新型号)

  • USB 电源适配器

  • 计算机显示器

  • USB 键盘

  • USB 鼠标

  • 一个小型舵机

  • 面包板

  • LED(任何颜色)

  • 面包板的跳线

将舵机连接到树莓派

这个项目涉及将舵机连接到我们的树莓派。许多人将舵机与步进电机和直流电机混淆。让我们来看看这些类型的电机之间的区别。

步进电机

步进电机是无刷直流电动机,可以移动等步长的完整旋转。电机的位置是在没有使用反馈系统(开环系统)的情况下控制的。这使得步进电机相对廉价,并且在机器人、3D 打印机和数控机床等应用中很受欢迎。

以下是步进电机内部工作的粗略图示:

通过按顺序打开和关闭线圈 A 和 B,可以旋转连接到电机轴的永磁体。使用精确的步骤,可以精确控制电机,因为步数可以轻松控制。

步进电机往往比其他类型的小型电机更重更笨重。

以下照片显示了 3D 打印机中使用的典型步进电机:

直流电机

直流电机与步进电机类似,但不会将运动分成相等的步骤。它们是最早被广泛使用的电动机,并且在电动汽车、电梯和任何不需要精确控制电机位置的应用中使用。直流电机可以是刷式或无刷的。

刷式电机操作起来更简单,但在每分钟转数(RPM)和使用寿命上有限制。无刷电机更复杂,需要电子控制,例如一些无人机上使用的电子调速器(ESC)。无刷电机可以以更高的转速运行,并且比刷式电机有更长的使用寿命。

直流电机的响应时间比步进电机短得多,并且比可比较的步进电机更轻。

以下是典型的小型刷式直流电机的照片:

舵机

舵机利用闭环反馈机制来提供对电机位置的极其精确的控制。它们被认为是步进电机的高性能替代品。范围可以根据舵机的不同而变化,有些舵机限制在 180 度运动,而其他舵机可以运动 360 度。

闭环控制系统与开环控制系统不同,它通过测量输出的实际条件并将其与期望的结果进行比较来维持输出。闭环控制系统通常被称为反馈控制系统,因为正是这种反馈被用来调整条件。

舵机的角度由传递到舵机控制引脚的脉冲决定。不同品牌的舵机具有不同的最大和最小值,以确定舵机指针的角度。

以下是一个图表,用于演示脉冲宽度调制PWM)与 180 度舵机位置之间的关系:

以下是我们将在电路中使用的小型舵机的照片。我们可以直接将这个舵机连接到我们的树莓派(较大的舵机可能无法实现):

以下是舵机颜色代码的图表:

将舵机连接到我们的树莓派

我们的电路将由一个简单的舵机和 LED 组成。

以下是电路的 Fritzing 图:

我们连接:

  • 舵机的正电源到 5V 直流电源,地到 GND

  • 从舵机到 GPIO 17 的控制信号

  • LED 的正极连接到 GPIO 14,电阻连接到 GND

确保使用小型舵机,因为较大的舵机可能需要比树莓派能够提供的更多电力。电路应该类似于以下内容:

通过命令行控制舵机

现在我们的舵机已连接到树莓派,让我们在命令行中编写一些代码来控制它。我们将使用树莓派 Python 库GPIO Zero来实现这一点。

加载 Thonny 并点击 Shell:

在 Shell 中输入以下内容:

from gpiozero import Servo

短暂延迟后,光标应该返回。我们在这里所做的是将gpiozero中的servo对象加载到内存中。我们将使用以下语句为引脚 GPIO 17分配:

servo = Servo(17)

现在,我们将舵机移动到最小(min)位置。在命令行中输入以下内容:

servo.min()

你应该听到舵机在移动,指针将移动到最远的位置(如果它还没有在那里)。

使用以下命令将舵机移动到最大(max)位置:

servo.max()

现在,使用以下命令将舵机移动到中间(mid)位置:

servo.mid()

舵机应该移动到其中间位置。

当你把手放在舵机上时,你可能会感到轻微的抽搐运动。要暂时禁用对舵机的控制,请在命令行中输入以下内容并按 Enter 键:

servo.detach()

抽搐运动应该停止,附在舵机上的指针指示器应该保持在当前位置。

正如我们所看到的,很容易将舵机移动到其最小、中间和最大值。但是如果我们想要更精确地控制舵机怎么办?在这种情况下,我们可以使用servo对象的 value 属性。可以使用介于-1(最小)和1(最大)之间的值来移动舵机。

在命令行中输入以下内容:

servo.value=-1

servo应该移动到其最小位置。现在,输入以下内容:

servo.value=1

servo现在应该移动到其最大位置。让我们使用 value 属性来指示天气条件。在命令行中输入以下内容:

weather_conditions = {'cloudy':-1, 'partly cloudy':-0.5, 'partly sunny': 0.5, 'sunny':1}

在 Shell 中使用以下代码进行测试:

weather_conditions['partly cloudy']

你应该在 Shell 中看到以下内容:

-0.5

有了我们的servo对象和我们的weather_conditions字典,我们现在可以使用伺服电机来物理地指示天气条件。在 shell 中输入以下内容:

servo.value = weather_conditions['cloudy']

伺服电机应该移动到最小位置,以指示天气条件为“多云”。现在,让我们尝试“晴朗”:

servo.value = weather_conditions['sunny']

伺服应该移动到最大位置,以指示“晴朗”的天气条件。

对于“局部多云”和“局部晴朗”的条件,使用以下内容:

servo.value = weather_conditions['partly cloudy']
servo.value = weather_conditions['partly sunny']

编写一个 Python 程序来控制伺服

杰瑞·塞范菲尔德曾开玩笑说,我们需要知道天气的全部信息就是:我们是否需要带上外套?在本章和下一章的其余部分中,我们将建立一个模拟仪表针仪表板,以指示天气条件所需的服装。

我们还将添加一个 LED,用于指示需要雨伞,并闪烁以指示非常恶劣的风暴。

在我们可以在第六章中构建仪表板之前,我们需要代码来控制伺服和 LED。我们将首先创建一个类来实现这一点。

这个类将在我们的电路上设置伺服位置和 LED 状态:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 打开 Thonny

  2. 单击新图标创建一个新文件

  3. 输入以下内容:

from gpiozero import Servo
from gpiozero import LED

class WeatherDashboard:

    servo_pin = 17
    led_pin = 14

    def __init__(self, servo_position=0, led_status=0):      
        self.servo = Servo(self.servo_pin)
        self.led = LED(self.led_pin)      
        self.move_servo(servo_position)
        self.set_led_status(led_status)

    def move_servo(self, servo_position=0): 
        self.servo.value=self.convert_percentage_to_integer
        (servo_position)

    def set_led_status(self, led_status=0):       
        if(led_status==0):
            self.led.off()
        elif (led_status==1):
            self.led.on()
        else:
            self.led.blink()

    def convert_percentage_to_integer(self, percentage_amount):
        return (percentage_amount*0.02)-1

if __name__=="__main__":
    weather_dashboard = WeatherDashboard(50, 1)
  1. 将文件保存为WeatherDashboard.py

  2. 运行代码

  3. 您应该看到伺服移动到中间位置,LED 应该打开

尝试其他值,看看是否可以将伺服移动到 75%并使 LED 闪烁。

让我们来看看代码。在定义类之后,我们使用以下内容为伺服和 LED 设置了 GPIO 引脚值:

servo_pin = 17
led_pin = 14

正如您在我们建立的电路中看到的那样,我们将伺服和 LED 分别连接到 GPIO17和 GPIO14。GPIO Zero 允许我们轻松地分配 GPIO 值,而无需样板代码。

在我们的类初始化方法中,我们分别创建了名为servoledServoLED对象:

self.servo = Servo(self.servo_pin)
self.led = LED(self.led_pin) 

从这里开始,我们调用我们类中移动伺服和设置 LED 的方法。让我们看看第一个方法:

def move_servo(self, servo_position=0): 
        self.servo.value=self.convert_percentage_to_integer
        (servo_position)

在这个方法中,我们只需设置servo对象中的值属性。由于此属性仅接受从-11的值,而我们传递的值是从0100,因此我们需要将我们的servo_position进行转换。我们使用以下方法来实现这一点:

def convert_percentage_to_integer(self, percentage_amount):
    return (percentage_amount*0.02)-1

为了将百分比值转换为-11的比例值,我们将百分比值乘以0.02,然后减去1。通过使用百分比值为50来验证这个数学问题是很容易的。值为50代表了0100比例中的中间值。将50乘以0.02得到了值为1。从这个值中减去1得到了0,这是-11比例中的中间值。

要设置 LED 的状态(关闭、打开或闪烁),我们从初始化方法中调用以下方法:

def set_led_status(self, led_status=0):       
    if(led_status==0):
        self.led.off()
    elif (led_status==1):
        self.led.on()
    else:
        self.led.blink()

set_led_status中,如果传入的值为0,我们将 LED 设置为“关闭”,如果值为1,我们将其设置为“打开”,如果是其他值,我们将其设置为“闪烁”。

我们用以下代码测试我们的类:

if __name__=="__main__":
    weather_dashboard = WeatherDashboard(50, 1)

在第六章中,使用伺服控制代码控制模拟设备,我们将使用这个类来构建我们的模拟天气仪表板。

总结

正如我们所看到的,使用树莓派轻松地将数字世界和模拟世界之间的差距进行数据显示。其 GPIO 端口允许轻松连接各种输出设备,如电机和 LED。

在本章中,我们连接了一个伺服电机和 LED,并使用 Python 代码对它们进行了控制。我们将在第六章中扩展这一点,使用伺服控制代码来控制模拟设备,构建一个带有模拟仪表盘显示的物联网天气仪表板。

问题

  1. 真还是假?步进电机是使用开环反馈系统控制的。

  2. 如果您要建造一辆电动汽车,您会使用什么类型的电动机?

  3. 真或假?舵机被认为是步进电机的高性能替代品。

  4. 是什么控制了舵机的角度?

  5. 真或假?直流电机的响应时间比步进电机短。

  6. 我们使用哪个 Python 包来控制我们的舵机?

  7. 真或假?我们能够在 Thonny 的 Python shell 中控制舵机。

  8. 用什么命令将舵机移动到最大位置?

  9. 真或假?我们只能将舵机移动到最小、最大和中间位置。

  10. 我们如何将百分比值转换为代码中servo对象理解的相应值?

进一步阅读

GPIO Zero文档提供了对这个令人惊叹的树莓派 Python 库的完整概述。了解更多信息,请访问gpiozero.readthedocs.io/en/stable/

第六章:使用伺服控制代码控制模拟设备

继续我们的旅程,将模拟仪表的优雅与数字数据的准确性相结合,我们将看看我们在前两章中学到的内容,并构建一个带有模拟仪表显示的物联网天气仪表盘。

在开始本章之前,请确保已经连接了第五章中的电路,使用 Python 控制伺服

这个仪表盘将根据室外温度和风速显示衣柜建议。我们还将在我们的仪表盘上使用 LED 指示是否需要带上雨伞。

本章将涵盖以下主题:

  • 从云端访问天气数据

  • 使用天气数据控制伺服

  • 增强我们的项目

完成本章所需的知识

您应该具备 Python 编程语言的工作知识才能完成本章。还必须了解如何使用简单的面包板,以便连接组件。

在这个项目中可以使用乙烯基或手工切割机。了解如何使用切割机将是一个资产,这样你就可以完成这个项目。

项目概述

到本章结束时,我们应该有一个可以工作的物联网模拟天气仪表盘。我们将修改第四章和第五章中编写的代码,以向我们的仪表盘提供数据。将打印并剪切一个背景。这个背景将给我们的仪表盘一个卡通般的外观。

我们将使用第五章中的电路,使用 Python 控制伺服。以下是来自该电路的接线图:

这个项目应该需要一个下午的时间来完成。

入门

要完成这个项目,需要以下设备:

  • 树莓派 3 型号(2015 年或更新型号)

  • 一个 USB 电源适配器

  • 一台电脑显示器

  • 一个 USB 键盘

  • 一个 USB 鼠标

  • 一个小型伺服电机

  • 一个 LED(任何颜色)

  • 一个面包板

  • 面包板的跳线线

  • 一个彩色打印机

  • 一个乙烯基或手工切割机(可选)

从云端访问天气数据

在第四章中,订阅 Web 服务,我们编写了一个 Python 程序,从 Yahoo!天气获取天气数据。该程序中的CurrentWeather类返回了根据类实例化时的city值返回的温度、天气状况和风速。

我们将重新访问该代码,并将类名更改为WeatherData。我们还将添加一个方法,返回一个值从0-100,以指示天气。在确定这个数字时,我们将考虑温度和风速,0 表示极端的冬季条件,100表示非常炎热的夏季极端条件。我们将使用这个数字来控制我们的伺服。我们还将检查是否下雨,并更新我们的 LED 以指示我们是否需要雨伞:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 打开 Thonny

  2. 单击新图标创建一个新文件

  3. 在文件中输入以下内容:

from weather import Weather, Unit

class WeatherData:

    temperature = 0
    weather_conditions = ''
    wind_speed = 0
    city = ''

    def __init__(self, city):
        self.city = city
        weather = Weather(unit = Unit.CELSIUS)
        lookup = weather.lookup_by_location(self.city)
        self.temperature = float(lookup.condition.temp)
        self.weather_conditions = lookup.condition.text
        self.wind_speed = float(lookup.wind.speed)

    def getServoValue(self):
        temp_factor = (self.temperature*100)/30
        wind_factor = (self.wind_speed*100)/20
        servo_value = temp_factor-(wind_factor/20)

        if(servo_value >= 100):
            return 100
        elif (servo_value <= 0):
            return 0
        else:
            return servo_value

    def getLEDValue(self): 
        if (self.weather_conditions=='Thunderstorm'):
            return 2;
        elif(self.weather_conditions=='Raining'):
            return 1
        else:
            return 0

if __name__=="__main__":

    weather = WeatherData('Paris')
    print(weather.getServoValue())
    print(weather.getLEDValue())
  1. 将文件保存为WeatherData.py

我们的代码的核心在于getServoValue()getLEDValue()方法:

def getServoValue(self):
     temp_factor = (self.temperature*100)/30
     wind_factor = (self.wind_speed*100)/20
     servo_value = temp_factor-(wind_factor/20)

     if(servo_value >= 100):
         return 100
     elif (servo_value <= 0):
         return 0
     else:
         return servo_value

getServoValue方法中,我们将temp_factorwind_factor变量设置为基于最小值0和温度和风速的最大值3020的百分比值。这些是任意的数字,因为我们将考虑30摄氏度为我们的极端高温,20 公里/小时的风速为我们的极端风速。伺服值通过从温度减去风速的 5%(除以20)来设置。当然,这也是任意的。随意调整所需的百分比。

为了进一步解释,考虑一下 10 摄氏度的温度和 5 公里/小时的风速。温度因子(temp_factor)将是 10 乘以 100,然后除以 30 或 33.33。风速因子(wind_factor)将是 5 乘以 100,然后除以 20 或 25。我们传递给舵机的值(servo_value)将是温度因子(33.33)减去风速因子(25)后除以20servo_value的值为 32.08,或者大约是最大舵机值的 32%。

然后返回servo_value的值并将其用于控制我们的舵机。任何低于0和高于100的值都将超出我们的范围,并且无法与我们的舵机一起使用(因为我们将舵机在0100之间移动)。我们在getServoValue方法中使用if语句来纠正这种情况。

getLEDValue方法只是检查天气条件并根据是否下雨返回代码。“雷暴”将返回值2,“雨”和“小雨”将返回值1,其他任何情况将返回值0。如果有雷暴,我们将使用这个值来在我们的仪表盘上闪烁 LED,如果只是下雨,我们将保持其亮起,并在其他所有情况下关闭它:

def getLEDValue(self):
     if (self.weather_conditions=='Thunderstorm'):
         return 2;
     elif(self.weather_conditions=='Rain'):
         return 1
     elif(self.weather_conditions=='Light Rain'):
         return 1
     else:
         return 0

在撰写本书时,“雷暴”、“雨”和“小雨”是在搜索世界各大城市天气时返回的值。请随时更新if语句以包括其他极端降水的描述。作为一个额外的增强,你可以考虑在if语句中使用正则表达式。

在 Thonny 中运行代码。你应该会得到巴黎天气条件下舵机和 LED 的值。我在运行代码时得到了以下结果:

73.075
0

使用天气数据控制舵机

我们即将构建我们的物联网天气仪表盘。最后的步骤涉及根据从 Yahoo! Weather 网络服务返回的天气数据来控制我们舵机的位置,并在物理上建立一个背景板来支撑我们的舵机指针。

校正舵机范围

正如你们中的一些人可能已经注意到的那样,你的舵机并不能从最小到最大移动 180 度。这是由于 GPIO Zero 中设置的最小和最大脉冲宽度为 1 毫秒和 2 毫秒。为了解决这个差异,我们在实例化Servo对象时必须相应地调整min_pulse_widthmax_pulse_width属性。

以下代码就是这样做的。变量servoCorrectionmin_pulse_widthmax_pulse_width值进行加减。以下代码在5秒后将舵机移动到最小位置,然后移动到最大位置:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 中打开 Thonny。

  2. 单击“新建”图标创建新文件。

  3. 在文件中键入以下内容:

from gpiozero import Servo
from time import sleep
servoPin=17

servoCorrection=0.5
maxPW=(2.0+servoCorrection)/1000
minPW=(1.0-servoCorrection)/1000

servo=Servo(servoPin, min_pulse_width=minPW, max_pulse_width=maxPW)

servo.min()
sleep(5)
servo.max()
sleep(5)
servo.min()
sleep(5)
servo.max()
sleep(5)
servo.min()
sleep(5)
servo.max()
sleep(5)

servo.close()
  1. 将文件保存为servo_correction.py

  2. 运行代码,看看servoCorrection的值是否修复了你的舵机在servo.minservo.max之间不能转动 180 度的问题。

  3. 调整servoCorrection,直到你的舵机在servo.minservo.max之间移动了 180 度。我们将在天气仪表盘的代码中使用servoCorrection的值。

根据天气数据改变舵机的位置

我们现在已经准备好根据天气条件控制我们舵机的位置。我们将修改我们在第五章中创建的WeatherDashboard类,用 Python 控制舵机,执行以下步骤:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 中打开 Thonny

  2. 单击“新建”图标创建新文件

  3. 在文件中键入以下内容:

from gpiozero import Servo
from gpiozero import LED
from time import sleep
from WeatherData import WeatherData

class WeatherDashboard:

     servo_pin = 17
     led_pin = 14
     servoCorrection=0.5
     maxPW=(2.0+servoCorrection)/1000
     minPW=(1.0-servoCorrection)/1000

     def __init__(self, servo_position=0, led_status=0):
         self.servo = Servo(self.servo_pin, min_pulse_width=
                self.minPW, max_pulse_width=self.maxPW)
         self.led = LED(self.led_pin)

         self.move_servo(servo_position)
         self.set_led_status(led_status)

     def move_servo(self, servo_position=0): 
         self.servo.value = self.convert_percentage_to_integer(
                servo_position)

     def turnOffServo(self):
         sleep(5)
         self.servo.close()

     def set_led_status(self, led_status=0):
         if(led_status==0):
             self.led.off()
         elif (led_status==1):
             self.led.on()
         else:
             self.led.blink()

     def convert_percentage_to_integer(self, percentage_amount):
        #adjust for servos that turn counter clockwise by default
        adjusted_percentage_amount = 100 - percentage_amount
        return (adjusted_percentage_amount*0.02)-1

if __name__=="__main__":
     weather_data = WeatherData('Toronto')
     weather_dashboard = WeatherDashboard(
     weather_data.getServoValue(),
     weather_data.getLEDValue())
     weather_dashboard.turnOffServo()
  1. 将文件保存为WeatherDashboard.py

  2. 运行代码并观察舵机位置的变化

让我们来看看代码。

我们首先导入我们需要的资源:

from time import sleep
from WeatherData import WeatherData

我们添加time到我们的项目中,因为我们将在关闭Servo对象之前使用它作为延迟。添加WeatherData以根据天气条件为我们的伺服和 LED 提供值。

servoCorrectionmaxPWminPW变量调整我们的伺服(如果需要),如前面的伺服校正代码所述:

servoCorrection=0.5
maxPW=(2.0+servoCorrection)/1000
minPW=(1.0-servoCorrection)/1000

turnOffServo方法允许我们关闭与伺服的连接,停止可能发生的任何抖动运动:

def turnOffServo(self):
    sleep(5)
    self.servo.close()

我们使用sleep函数延迟关闭伺服,以便在设置到位置之前不会关闭。

您可能还注意到了代码中convert_percentage_to_integer方法的更改第五章中的代码,使用 Python 控制伺服。为此项目测试的电机在右侧有一个最小位置。这与我们所需的相反,因此代码已更改为从 100 中减去percentage_amount,以扭转此行为并给出正确的伺服位置(有关此方法的更多信息,请参阅第五章,使用 Python 控制伺服,如有需要,请使用本章中的convert_percentage_to_integer):

def convert_percentage_to_integer(self, percentage_amount):
        #adjust for servos that turn counter clockwise by default
        adjusted_percentage_amount = 100 - percentage_amount
        return (adjusted_percentage_amount*0.02)-1

在 Thonny 中运行代码。您应该看到伺服电机根据多伦多,加拿大的天气条件移动到一个位置。LED 将根据多伦多的降雨情况闪烁、保持稳定或关闭。

现在,让我们通过为我们的伺服和 LED 建立一个物理背景来增强我们的项目。

增强我们的项目

现在我们的代码已经完成,现在是时候为我们的伺服添加一个物理背景了。通过这个背景,我们根据天气数据为我们的衣柜推荐穿什么。

打印主图形

以下是我们将在背景中使用的图形:

使用彩色打印机,在可打印的乙烯基上打印图形(此图像文件可从我们的 GitHub 存储库中获取)。剪下伞下和主图形下的孔。

为了增加支撑,用刀或剪刀在硬纸板上切出背板:

将背景从可打印的乙烯基片上剥离并粘贴到背板上。使用孔将背景与背板对齐:

添加指针和 LED

将 LED 插入伞下的孔中:

将伺服电机的轴心插入另一个孔。如有必要,使用双面泡沫胶带将伺服固定在背板上:

使用跳线线将 LED 和伺服连接到面包板上(请参阅本章开头的接线图)。组件应该稍微倾斜。在我们用新的显示运行WeatherDashboard代码之前,我们必须将指针安装到最小位置:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 单击新图标创建一个新文件

  3. 在文件中输入以下内容:

from gpiozero import Servo
servoPin=17

servoCorrection=<<put in the correction you calculated>>
maxPW=(2.0+servoCorrection)/1000
minPW=(1.0-servoCorrection)/1000

servo=Servo(servoPin, min_pulse_width=minPW, max_pulse_width=maxPW)

servo.min()
  1. 将文件保存为servo_minimum.py

  2. 运行代码使伺服将自己定位到最小值

安装指针,使其指向左侧,如果伺服电机逆时针转到最小位置,使其指向右侧,如果伺服电机顺时针转到最小位置(一旦您开始实际使用伺服,这将更有意义)。

再次运行WeatherDashboard代码。伺服应该根据天气数据移动,指示衣柜选项。如果下雨,LED 应该亮起。雷暴会闪烁 LED。否则,LED 将保持关闭状态。

在下图中,仪表盘建议多伦多,加拿大穿短袖衬衫。外部天气条件不需要雨伞:

恭喜!你刚刚建立了一个 IoT 天气仪表盘。

摘要

在这个项目中,我们利用了树莓派的力量来创建了一个 IoT 模拟天气仪表盘。在这种情况下,这涉及到使用作为模拟仪表的互联网控制的伺服。我们很容易想象如何修改我们的代码来显示除天气数据之外的其他数据。想象一下,一个模拟仪表显示远程工厂的油箱水平,其中水平数据通过互联网通信。

模拟仪表的直观性使其非常适合需要一瞥数据的应用程序。将模拟仪表与来自互联网的数据结合起来,创造了全新的数据显示世界。

在第七章中,设置树莓派 Web 服务器,我们将迈出模拟世界的一步,探索如何使用树莓派作为 Web 服务器并构建基于 Web 的仪表盘。

问题

  1. 真还是假?伺服可以用作 IoT 设备。

  2. 真还是假?更改Servo对象上的最小和最大脉冲宽度值会修改伺服的范围。

  3. 为什么在调用Servo对象的close()方法之前我们要添加延迟?

  4. 真还是假?我们在WeatherData类中不需要getTemperature()方法。

  5. 真还是假?我们仪表盘上闪烁的 LED 表示晴天和多云的天气。

  6. 我们在仪表盘上使用一对短裤来表示什么?

  7. 在我们的代码中,你会在哪里使用正则表达式?

  8. 为什么我们在代码中导入时间?

  9. 真还是假?IoT 启用的伺服只能用于指示天气数据。

进一步阅读

为了增强我们的代码,可以使用正则表达式。任何关于 Python 和正则表达式的文档都对发展强大的编码技能非常宝贵。

第七章:设置树莓派 Web 服务器

我们将通过学习如何使用 CherryPy web 服务器框架来开始创建 IoT 家庭安全仪表板的旅程。我们的章节将从介绍 CherryPy 开始。在我们创建一个修改版本的CurrentWeather类的 HTML 天气仪表板之前,我们将通过一些示例进行学习。

本章将涵盖以下主题:

  • 介绍 CherryPy——一个极简的 Python Web 框架

  • 使用 CherryPy 创建一个简单的网页

完成本章所需的知识

读者应该具有 Python 的工作知识才能完成本章。还需要基本了解 HTML,包括 CSS,才能完成本章的项目。

项目概述

在本章中,我们将使用 CherryPy 和 Bootstrap 框架构建 HTML 天气仪表板。不需要对这些框架有深入的了解就可以完成项目。

这个项目应该需要几个小时来完成。

入门

要完成此项目,需要以下内容:

  • Raspberry Pi Model 3(2015 年或更新型号)

  • 一个 USB 电源适配器

  • 一个计算机显示器

  • 一个 USB 键盘

  • 一个 USB 鼠标

介绍 CherryPy——一个极简的 Python Web 框架

对于我们的项目,我们将使用 CherryPy Python 库(请注意,它是带有"y"的 CherryPy,而不是带有"i"的 CherryPi)。

什么是 CherryPy?

根据他们的网站,CherryPy 是一个 Pythonic 的面向对象的 Web 框架。CherryPy 使开发人员能够构建 Web 应用程序,就像他们构建任何面向对象的 Python 程序一样。按照 Python 的风格,CherryPy 程序的代码更少,开发时间比其他 Web 框架短。

谁在使用 CherryPy?

一些使用 CherryPy 的公司包括以下内容:

  • Netflix:Netflix 通过 RESTful API 调用在其基础设施中使用 CherryPy。Netflix 使用的其他 Python 库包括 Bottle 和 SciPy。

  • Hulu:CherryPy 被用于 Hulu 的一些项目。

  • Indigo Domotics:Indigo Domotics 是一家使用 CherryPy 框架的家庭自动化公司。

安装 CherryPy

我们将使用 Python 的pip3软件包管理系统来安装 CherryPy。

软件包管理系统是一个帮助安装和配置应用程序的程序。它还可以进行升级和卸载。

要做到这一点,打开一个终端窗口,输入以下内容:

sudo pip3 install cherrypy

按下Enter。您应该在终端中看到以下内容:

在 Thonny 中,转到工具|管理包。您应该看到 CherryPy 现在已安装,如下所示:

使用 CherryPy 创建一个简单的网页

让我们开始,让我们用 CherryPy 构建最基本的程序。我指的当然是无处不在的Hello World程序,我们将用它来说Hello Raspberry Pi!。在我们构建一个仪表板来显示天气数据之前,我们将通过一些示例进行学习,使用第四章中的CurrentWeather类的修改版本,订阅 Web 服务

Hello Raspberry Pi!

要构建Hello Raspberry Pi!网页,执行以下操作:

  1. 从应用程序菜单|编程|Thonny Python IDE 中打开 Thonny。

  2. 单击新图标创建一个新文件。

  3. 输入以下内容:

import cherrypy

class HelloWorld():

     @cherrypy.expose
     def index(self):
         return "Hello Raspberry Pi!"

cherrypy.quickstart(HelloWorld())

  1. 确保行cherrypy.quickstart(HelloWorld())importclass语句一致。

  2. 将文件保存为HelloRaspberryPi.py

  3. 点击绿色的Run current script按钮运行文件。

  4. 您应该看到 CherryPy web 服务器正在终端中启动:

  1. 从终端输出中,您应该能够观察到 CherryPy 正在运行的 IP 地址和端口,http://127.0.0.1:8080。您可能会认出 IP 地址是环回地址。CherryPy 使用端口8080

  2. 在树莓派上打开一个网络浏览器,并在地址栏中输入上一步的地址:

恭喜,您刚刚将您的谦卑的树莓派变成了一个网络服务器。

如果您和我一样,您可能没有想到一个网络服务器可以用如此少的代码创建。CherryPy 基本上专注于一个任务,那就是接收 HTTP 请求并将其转换为 Python 方法。

它是如何工作的呢?我们的HelloWorld类中的装饰器@cherrypy.expose公开了恰好对应于网络服务器根目录的index方法。当我们使用回环地址(127.0.0.1)和 CherryPy 正在运行的端口(8080)加载我们的网页时,index方法将作为页面提供。在我们的代码中,我们只是返回字符串Hello Raspberry Pi!,然后它就会显示为我们的网页。

回环地址是用作机器软件回环接口的 IP 号码。这个号码通常是127.0.0.1。这个地址并没有物理连接到网络,通常用于测试安装在同一台机器上的网络服务器。

向 myFriend 问好

那么,如果我们在 Python 代码中公开另一个方法会发生什么呢?我们可以通过在方法之前使用装饰器轻松地检查到这一点。让我们编写一些代码来做到这一点:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 中打开 Thonny。

  2. 单击新建图标创建一个新文件。

  3. 输入以下内容:

import cherrypy

class HelloWorld():

     @cherrypy.expose
     def index(self):
         return "Hello Raspberry Pi!"

     @cherrypy.expose
     def sayHello(self, myFriend=" my friend"):
         return "Hello " + myFriend

cherrypy.quickstart(HelloWorld())
  1. 将文件保存为SayHello.py

  2. 通过单击中断/重置按钮,然后单击运行当前脚本按钮来停止和启动 CherryPy 服务器。

  3. 现在,输入以下内容到您的浏览器的地址栏中,然后按Enterhttp://127.0.0.1:8080/sayHello

  4. 您应该看到以下内容:

这次我们做了什么不同的事情?首先,我们不只是访问了服务器的根目录。我们在 URL 中添加了/sayHello。通常,当我们在网络服务器上这样做时,我们会被引导到一个子文件夹。在这种情况下,我们被带到了我们的HelloWorld类中的sayHello()方法。

如果我们仔细看sayHello()方法,我们会发现它接受一个名为myFriend的参数:

@cherrypy.expose
def sayHello(self, myFriend=" my friend"):
         return "Hello " + myFriend

我们可以看到myFriend参数的默认值是my Friend。因此,当我们运行 CherryPy 并导航到http://127.0.0.1:8080/sayHello的 URL 时,将调用sayHello方法,并返回"Hello " + my friend字符串。

现在,将以下内容输入到地址栏中,然后按Enterhttp://127.0.0.1:8080/sayHello?myFriend=Raspberry%20Pi

在这个 URL 中,我们将myFriend的值设置为Raspberry%20Pi(使用%20代替空格)。我们应该得到与我们的第一个示例相同的结果。

正如我们所看到的,将 Python 方法连接到 HTML 输出非常容易。

静态页面呢?

静态页面曾经是互联网上随处可见的。静态页面之间的简单链接构成了当时被认为是一个网站的内容。然而,自那时以来发生了很多变化,但是能够提供一个简单的 HTML 页面仍然是网络服务器框架的基本要求。

那么,我们如何在 CherryPy 中做到这一点呢?实际上很简单。我们只需在一个方法中打开一个静态 HTML 页面并返回它。让我们通过以下方式让 CherryPy 提供一个静态页面:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 中打开 Thonny。

  2. 单击新建图标创建一个新文件。

  3. 输入以下内容:

<html>
    <body>
        This is a static HTML page.
    </body>
</html>
  1. 将文件保存为static.html

  2. 在 Thonny 中点击新建图标,在与static.html相同的目录中创建一个新文件。

  3. 输入以下内容:

import cherrypy

class StaticPage():

     @cherrypy.expose
     def index(self):
         return open('static.html')

cherrypy.quickstart(StaticPage())
  1. 将文件保存为StaticPage.py

  2. 如果 CherryPy 仍在运行,请单击红色按钮停止它。

  3. 运行文件StaticPage.py以启动 CherryPy。

  4. 您应该看到 CherryPy 正在启动,如终端中所示。

  5. 要查看我们的新静态网页,请在树莓派上打开一个网络浏览器,并在地址栏中输入以下内容:http://127.0.0.1:8080

  6. 您应该看到静态页面显示出来了:

那么我们在这里做了什么呢?我们修改了我们的index方法,使其返回一个打开的static.html文件,带有return open('static.html')这一行。这将在我们的浏览器中打开static.html作为我们的索引(或http://127.0.0.1:8080/index)。请注意,尝试在 url 中输入页面名称static.htmlhttp://127.0.0.1:8080/static.html)将不起作用。CherryPy 根据方法名提供内容。在这种情况下,方法名是 index,这是默认值。

HTML 天气仪表板

现在是时候添加我们从之前章节学到的知识了。让我们重新访问第四章中的CurrentWeather类,订阅 Web 服务。我们将把它重命名为WeatherData,因为这个名字更适合这个项目,并稍微修改一下。

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 中打开 Thonny

  2. 单击新图标创建一个新文件

  3. 输入以下内容:

from weather import Weather, Unit
import time

class WeatherData:

    temperature = 0
    weather_conditions = ''
    wind_speed = 0
    city = ''

    def __init__(self, city):
        self.city = city
        weather = Weather(unit = Unit.CELSIUS)
        lookup = weather.lookup_by_location(self.city)
        self.temperature = lookup.condition.temp
        self.weather_conditions = lookup.condition.text
        self.wind_speed = lookup.wind.speed

    def getTemperature(self):
        return self.temperature + " C"

    def getWeatherConditions(self):
        return self.weather_conditions

    def getWindSpeed(self):
        return self.wind_speed + " kph"

    def getCity(self):
        return self.city

    def getTime(self):
        return time.ctime()

if __name__ == "__main__":

    current_weather = WeatherData('London')
    print(current_weather.getTemperature())
    print(current_weather.getWeatherConditions())
    print(current_weather.getTime())
  1. 将文件保存为WeatherData.py

  2. 运行代码

  3. 您应该在以下 shell 中看到伦敦,英格兰的天气:

让我们来看看代码。基本上WeatherData.py与第四章中的CurrentWeather.py完全相同,但多了一个名为getTime的额外方法:

def getTime(self):
    return time.ctime()

我们使用这种方法返回调用天气 Web 服务时的时间,以便在我们的网页中使用。

我们现在将使用 CherryPy 和Bootstrap框架来创建我们的仪表板。要做到这一点,请执行以下操作:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 中打开 Thonny

  2. 单击新图标创建一个新文件

  3. 输入以下内容(特别注意引号):

import cherrypy
from WeatherData import WeatherData

class WeatherDashboardHTML:

    def __init__(self, currentWeather):
        self.currentWeather = currentWeather

    @cherrypy.expose
    def index(self):
        return """
               <!DOCTYPE html>
                <html lang="en">

                <head>
                    <title>Weather Dashboard</title>
                    <meta charset="utf-8">
                    <meta name="viewport" content="width=device-width, initial-scale=1">
                    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css">
                    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
                    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"></script>
                    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>
                    <style>
                        .element-box {
                            border-radius: 10px;
                            border: 2px solid #C8C8C8;
                            padding: 20px;
                        }

                        .card {
                            width: 600px;
                        }

                        .col {
                            margin: 10px;
                        }
                    </style>
                </head>

                <body>
                    <div class="container">
                        <br/>
                        <div class="card">
                            <div class="card-header">
                                <h3>Weather Conditions for """ + self.currentWeather.getCity() + """
                                </h3></div>
                             <div class="card-body">
                                <div class="row">
                                    <div class="col element-box">
                                        <h5>Temperature</h5>
                                        <p>""" + self.currentWeather.getTemperature() + """</p>
                                    </div>
                                    <div class="col element-box">
                                        <h5>Conditions</h5>
                                        <p>""" + self.currentWeather.getWeatherConditions() + """</p>
                                    </div>
                                    <div class="col element-box">
                                        <h5>Wind Speed</h5>
                                        <p>""" + self.currentWeather.getWindSpeed() + """</p>
                                    </div>
                                </div>
                            </div>
                            <div class="card-footer"><p>""" + self.currentWeather.getTime() + """</p></div>
                        </div>
                    </div>
                </body>

                </html>
               """

if __name__=="__main__":
    currentWeather = WeatherData('Paris')
    cherrypy.quickstart(WeatherDashboardHTML(currentWeather))
  1. 将文件保存为WeatherDashboardHTML.py

这可能看起来是一大堆代码 - 而且确实是。不过,如果我们把它分解一下,实际上并不是那么复杂。基本上,我们使用 CherryPy 返回一个 HTML 字符串,这个字符串将通过index方法在我们的 URL 根目录中提供。

在我们可以这样做之前,我们通过传入一个WeatherData对象来实例化WeatherDashboardHTML类。我们给这个WeatherData对象起名为currentWeather,就像init(类构造函数)方法中所示的那样:

def __init__(self, currentWeather):
         self.currentWeather = currentWeather

CherryPy 通过打印一个 HTML 字符串来提供index方法,该字符串中包含来自我们currentWeather对象的参数。我们在我们的 HTML 代码中使用了 Bootstrap 组件库。我们通过合并标准的 Bootstrap 样板代码来添加它:

<link rel="stylesheet"href="https://maxcdn.bootstrapcdn.com
        /bootstrap/4.1.0/css/bootstrap.min.css">

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>

我们使用 Bootstrap 的card组件作为我们的内容容器。card允许我们创建一个标题、正文和页脚:

<div class="card">
    <div class="card-header">
        .
        .
        .

card组件的标题部分显示了城市的名称。我们使用我们的currentWeather对象的getCity方法来获取城市的名称。

<div class="card-header">
    <h3>Weather Conditions for """ + self.currentWeather.getCity() + """</h3>
</div>

card组件的正文部分,我们创建了一个具有三列的行。每列包含一个标题(<h5>),以及从我们的WeatherData对象currentWeather中提取的数据。您可以看到标题Temperature,以及从currentWeather方法getTemperature中提取的温度值:

<div class="card-body">
    <div class="row">
        <div class="col element-box">
            <h5>Temperature</h5>
            <p>""" + self.currentWeather.getTemperature() + """</p>
        .
        .
        .

对于页脚,我们只需返回currentWeather对象的实例化时间。我们将这个时间作为我们的程序从中检查天气信息的时间。

<div class="card-footer">
    <p>""" + self.currentWeather.getTime() + """</p>
</div>

我们在顶部的样式部分允许我们自定义我们的仪表板的外观。我们创建了一个 CSS 类,称为element-box,以便在我们的天气参数周围创建一个银色(#C8C8C8)的圆角框。我们还限制了卡片(因此也是仪表板)的宽度为600px。最后,我们在列周围放置了10px的边距,以便圆角框不会彼此接触:

<style>
    .element-box {
        border-radius: 10px;
        border: 2px solid #C8C8C8;
        padding: 20px;
    }

    .card {
        width: 600px;
    }

    .col {
        margin: 10px;
    }

</style>

我们在底部的main方法中将WeatherData类实例化为一个名为currentWeather的对象。在我们的示例中,我们使用来自Paris城市的数据。然后我们的代码将currentWeather对象传递给cherrypy.quickstart()方法,如下所示:

if __name__=="__main__":
     currentWeather = WeatherData('Paris')
     cherrypy.quickstart(WeatherDashboardHTML(currentWeather))

WeatherDashboardHTML.py文件上停止和启动 CherryPy 服务器。如果您的代码没有任何错误,您应该会看到类似以下的内容:

摘要

在本章中,我们使用 CherryPy HTTP 框架将我们的树莓派变成了一个 Web 服务器。凭借其简约的架构,CherryPy 允许开发者在很短的时间内建立一个支持 Web 的 Python 程序。

我们在本章开始时在树莓派上安装了 CherryPy。经过几个简单的示例后,我们通过修改和利用我们在第四章中编写的 Web 服务代码,构建了一个 HTML 天气仪表盘。

在接下来的章节中,我们将利用本章学到的知识来构建一个物联网家庭安全仪表盘。

问题

  1. True 或 false?它是 CherryPi,而不是 CherryPy。

  2. True 或 false?Netflix 使用 CherryPy。

  3. 我们如何告诉 CherryPy 我们想要公开一个方法?

  4. True 或 false?CherryPy 需要很多样板代码。

  5. 我们为什么将我们的CurrentWeather类重命名为WeatherData

  6. True 或 false?CherryPy 使用的默认端口是8888

  7. 我们为什么要向我们的col CSS 类添加边距?

  8. 我们从WeatherData类中使用哪个方法来获取当前天气状况的图片 URL?

  9. 我们使用哪个 Bootstrap 组件作为我们的内容容器?

  10. True 或 false?在我们的示例中,伦敦是晴天和炎热的。

更多阅读

在本章中,我们只是浅尝了一下 CherryPy 和 Bootstrap 框架。更多阅读材料可以在 CherryPy 网站上找到,网址为www.cherrypy.org,以及 Bootstrap 的网站,网址为getbootstrap.com。建议开发者通过阅读来提高对这些强大框架的了解。

第八章:使用 Python 读取树莓派 GPIO 传感器数据

在第七章中,设置树莓派 Web 服务器,我们使用 GPIO Zero 库来控制舵机和 LED 灯。在本章中,我们将使用 GPIO Zero 来读取 GPIO 端口的输入。首先,我们将从一个简单的按钮开始,然后转向被动红外PIR)运动传感器和蜂鸣器。

能够从 GPIO 读取传感器数据将使我们能够构建我们的物联网家庭安全仪表板。在本章结束时,我们应该对使用连接到 GPIO 的组件编程树莓派非常熟悉。

本章将涵盖以下主题:

  • 读取按钮的状态

  • 从红外运动传感器读取状态

  • 使用红外传感器修改Hello LED

项目概述

在本章中,我们将创建两种不同类型的报警系统。我们将首先学习如何从按钮读取 GPIO 传感器数据。然后,我们将学习如何与 PIR 传感器和距离传感器交互。最后,我们将学习如何连接一个有源蜂鸣器。

本章应该需要大约 3 小时完成。

入门

要完成这个项目,需要以下材料:

  • 树莓派 3 型(2015 年或更新型号)

  • 一个 USB 电源供应

  • 一台电脑显示器

  • 一个 USB 键盘

  • 一个 USB 鼠标

  • 一个面包板

  • 跳线

  • 一个 PIR 传感器

  • 一个距离传感器

  • 一个有源蜂鸣器

  • 一个 LED

  • 一个按钮(瞬时)

  • 一个按钮(锁定式)

  • 一个键开关(可选)

读取按钮的状态

Button,来自GPIO Zero库,为我们提供了一种与连接到 GPIO 的典型按钮进行交互的简单方法。本节将涵盖以下内容:

  • 使用 GPIO Zero 与按钮

  • 使用 Sense HAT 模拟器和 GPIO Zero 按钮

  • 使用长按按钮切换 LED

使用 GPIO Zero 与按钮

使用 GPIO 连接按钮相对容易。以下是显示连接过程的连接图:

将按钮连接,使一端使用跳线连接到地。将另一端连接到树莓派上的 GPIO 4。

在 Thonny 中,创建一个新文件并将其命名为button_press.py。然后,输入以下内容并运行:

from gpiozero import Button
from time import sleep

button = Button(4)
while True:
    if button.is_pressed:
     print("The Button on GPIO 4 has been pressed")
     sleep(1)

当你按下按钮时,你现在应该在壳中看到消息“GPIO 4 上的按钮已被按下”。代码将持续运行,直到你点击重置按钮。

让我们来看看代码。我们首先从GPIO Zero导入Button,并从time库导入sleep

from gpiozero import Button
from time import sleep

然后,我们创建一个新的button对象,并使用以下代码将其分配给 GPIO 引脚4

button = Button(4)

我们的连续循环检查按钮当前是否被按下,并在壳中打印出一条语句:

while True:
    if button.is_pressed:
     print("The Button on GPIO 4 has been pressed")
     sleep(1)

使用 Sense HAT 模拟器和 GPIO Zero 按钮

我们每天都使用按钮,无论是在电梯中选择楼层还是启动汽车。现代技术使我们能够将按钮与其控制的物理设备分离。换句话说,按下按钮可以引发许多与按钮无关的事件。我们可以使用我们的按钮和 Sense HAT 模拟器来模拟这种分离。

我可以想象你们中的一些人在想分离按钮与其控制对象实际意味着什么。为了帮助你们形象化,想象一下一个控制灯的锁定式按钮。当按钮被按下时,电路闭合,电流通过按钮上的引线流动。通过使用控制器和计算机,比如树莓派,按钮所需做的就是改变它的状态。控制器或计算机接收该状态并执行与按钮本身完全分离的操作。

从 Raspbian 的编程菜单中加载 Sense HAT 模拟器。在 Thonny 中创建一个名为sense-button.py的新的 Python 文件。输入以下代码到文件中,然后在完成后单击运行图标:

from gpiozero import Button
from sense_emu import SenseHat
from time import sleep

button = Button(4)
sense = SenseHat()

def display_x_mark(rate=1):
    sense.clear()
    X = (255,0,0)
    O = (255,255,255)
    x_mark = [
              X,O,O,O,O,O,O,X,
              O,X,O,O,O,O,X,O,
              O,O,X,O,O,X,O,O,
              O,O,O,X,X,O,O,O,
              O,O,O,X,X,O,O,O,
              O,O,X,O,O,X,O,O,
              O,X,O,O,O,O,X,O,
              X,O,O,O,O,O,O,X
    ]
    sense.set_pixels(x_mark)

while True:
    if button.is_pressed:
        display_x_mark()
        sleep(1)
    else:
        sense.clear()

如果您的代码没有任何错误,当您按下按钮时,您应该会看到 Sense HAT 模拟器上的显示屏变成白色背景上的红色X

让我们稍微解释一下上面的代码。我们首先导入我们代码所需的库:

from gpiozero import Button
from sense_emu import SenseHat
from time import sleep

然后我们创建新的按钮和 Sense HAT 模拟器对象。我们的button再次连接到 GPIO 引脚4

button = Button(4)
sense = SenseHat()

display_x_mark方法通过使用SenseHat方法set_pixels在显示器上创建一个X

def display_x_mark(rate=1):
    sense.clear()
    X = (255,0,0)
    O = (255,255,255)
    x_mark = [
              X,O,O,O,O,O,O,X,
              O,X,O,O,O,O,X,O,
              O,O,X,O,O,X,O,O,
              O,O,O,X,X,O,O,O,
              O,O,O,X,X,O,O,O,
              O,O,X,O,O,X,O,O,
              O,X,O,O,O,O,X,O,
              X,O,O,O,O,O,O,X
    ]
    sense.set_pixels(x_mark)

XO变量用于保存颜色代码,(255,0,0)表示红色,(255,255,255)表示白色。变量x_mark创建一个与 Sense HAT 模拟器屏幕分辨率匹配的 8 x 8 图案。x_mark被传递到SenseHAT对象的set_pixels方法中。

我们的连续循环检查按钮的is_pressed状态,并在状态返回true时调用display_x_mark方法。然后该方法会在白色背景上打印一个红色的X

当按钮未处于按下状态时,使用sense.clear()清除显示:

while True:
    if button.is_pressed:
        display_x_mark()
        sleep(1)
    else:
        sense.clear()

使用长按按钮切换 LED

使用GPIO Zero库,我们不仅可以检测按钮何时被按下,还可以检测按下多长时间。我们将使用hold_time属性和when_held方法来确定按钮是否被按下了一段时间。如果超过了这段时间,我们将打开和关闭 LED。

以下是我们程序的电路图。将按钮连接到 GPIO 引脚 4。使用 GPIO 引脚 17 来连接 LED,如图所示:

在 Thonny 中创建一个名为buttonheld-led.py的新文件。输入以下内容并单击运行:

from gpiozero import LED
from gpiozero import Button

led = LED(17)
button = Button(4)
button.hold_time=5

while True:
    button.when_held = lambda: led.toggle()

按下按钮保持5秒。您应该会看到 LED 切换开。现在再按下5秒。LED 应该会切换关闭。

我们已经在之前的示例中涵盖了代码的前四行。让我们看看按钮的保持时间是如何设置的:

button.hold_time=5

这一行将按钮的保持时间设置为5秒。when_held方法在我们的连续循环中被调用:

button.when_held = lambda: led.toggle()

使用 lambda,我们能够创建一个匿名函数,以便我们可以在LED对象led上调用toggle()。这会将 LED 打开和关闭。

从红外运动传感器中读取状态

使用运动传感器的报警系统是我们社会中无处不在的一部分。使用我们的树莓派,它们非常容易构建。我们将在本节中涵盖以下内容:

  • 什么是 PIR 传感器?

  • 使用GPIO 蜂鸣器

  • 构建一个基本的报警系统

什么是 PIR 传感器?

PIR 传感器是一种运动传感器,用于检测运动。 PIR 传感器的应用主要基于为安全系统检测运动。 PIR 代表被动红外线,PIR 传感器包含一个检测低级辐射的晶体。 PIR 传感器实际上是由两半构成的,因为两半之间的差异才能检测到运动。以下是一个廉价的 PIR 传感器的照片:

在上面的照片中,我们可以看到正(+)、负(-)和信号(S)引脚。这个特定的 PIR 传感器很适合面包板上。

以下是我们 PIR 电路的接线图。正极引脚连接到树莓派的 5V DC 输出。负极引脚连接到地(GND),信号引脚连接到 GPIO 引脚 4:

在 Thonny 中创建一个名为motion-sensor.py的新的 Python 文件。输入以下代码并运行:

from gpiozero import MotionSensor
from time import sleep

motion_sensor = MotionSensor(4)

while True:
    if motion_sensor.motion_detected:
        print('Detected Motion!')
        sleep(2)
    else:
        print('No Motion Detected!')
        sleep(2)

当您靠近 PIR 传感器时,您应该看到一条消息,上面写着检测到运动!。尝试保持静止,看看是否可以在 shell 中显示消息未检测到运动!

我们的代码开始时从GPIO Zero库中导入MotionSensor类:

from gpiozero import MotionSensor

导入sleep类后,我们创建一个名为motion_sensor的新MotionSensor对象,附加了数字4,以便让我们的程序在 GPIO 引脚 4 上寻找信号:

motion_sensor = MotionSensor(4)

在我们的循环中,我们使用以下代码检查motion_sensor是否有运动:

if motion_sensor.motion_detected:

从这里开始,我们定义要打印到 shell 的消息。

使用 GPIO Zero 蜂鸣器类

通常,有两种类型的电子蜂鸣器:有源和无源。有源蜂鸣器具有内部振荡器,当直流(DC)施加到它时会发出声音。无源蜂鸣器需要交流(AC)才能发出声音。无源蜂鸣器基本上是小型电磁扬声器。区分它们的最简单方法是施加直流电源并听声音。对于我们的代码目的,我们将使用有源蜂鸣器,如下图所示:

GPIO Zero库中有一个buzzer类。我们将使用这个类来生成有源蜂鸣器的刺耳警报声。按照以下图表配置电路。有源蜂鸣器的正极导线连接到 GPIO 引脚 17:

在 Thonny 中创建一个新的 Python 文件,并将其命名为buzzer-test1.py。输入以下代码并运行:

from gpiozero import Buzzer
from time import sleep

buzzer = Buzzer(17)

while True:
    buzzer.on()
    sleep(2)
    buzzer.off()
    sleep(2)

根据您选择的有源蜂鸣器,您应该听到持续两秒的刺耳声音,然后是 2 秒的静音。以下一行打开了蜂鸣器:

buzzer.on()

同样,前面代码中的这行关闭了蜂鸣器:

buzzer.off()

可以使用buzzer对象上的toggle方法简化代码。在 Thonny 中创建一个新的 Python 文件。将其命名为buzzer-test2.py。输入以下内容并运行:

from gpiozero import Buzzer
from time import sleep

buzzer = Buzzer(17)

while True:
    buzzer.toggle()
    sleep(2)

您应该得到相同的结果。执行相同操作的第三种方法是使用buzzer对象的beep方法。在 Thonny 中创建一个新的 Python 文件。将其命名为buzzer-test3.py。输入以下内容并运行:

from gpiozero import Buzzer

buzzer = Buzzer(17)

while True:
    buzzer.beep(2,2,10,False)

buzzer应该在2秒内打开,然后关闭2秒,重复进行10次。beep方法接受以下四个参数:

  • on_time:这是声音开启的秒数。默认值为1秒。

  • off_time:这是声音关闭的秒数。默认值为1秒。

  • n:这是进程运行的次数。默认值为None,表示永远。

  • background:这确定是否启动后台线程来运行进程。True值在后台线程中运行进程并立即返回。当设置为False时,直到进程完成才返回(请注意,当nNone时,该方法永远不会返回)。

构建一个基本的报警系统

现在让我们围绕蜂鸣器构建一个基本的报警系统。将 PIR 传感器连接到 GPIO 引脚 4,并将一个锁定按钮连接到 GPIO 引脚 8。以下是我们系统的电路图:

在 Thonny 中创建一个新文件,并将其命名为basic-alarm-system.py。输入以下内容,然后点击运行:

from gpiozero import MotionSensor
from gpiozero import Buzzer
from gpiozero import Button
from time import sleep

buzzer = Buzzer(17)
motion_sensor = MotionSensor(4)
switch = Button(8)

while True:
    if switch.is_pressed:
        if motion_sensor.motion_detected:
            buzzer.beep(0.5,0.5, None, True)
            print('Intruder Alert')
            sleep(1)
        else:
            buzzer.off()
            print('Quiet')
            sleep(1)
    else:
        buzzer.off()
        sleep(1)

我们在这里所做的是使用我们的组件创建一个报警系统。我们使用一个锁定按钮来打开和关闭报警系统。我们可以很容易地用一个钥匙开关替换锁定按钮。以下图片显示了这个变化:

这个电路可以很容易地转移到项目盒中,用作报警系统。

使用红外传感器修改 Hello LED

我们将通过修改我们最初的Hello LED代码来继续探索传感器。在这个项目中,我们将距离传感器与我们的 PIR 传感器相结合,并根据这些传感器的值闪烁 LED。这个电路不仅会告诉我们有人靠近,还会告诉我们他们有多近。

我们将在本节中涵盖以下内容:

  • 配置距离传感器

  • Hello LED提升到另一个水平

配置距离传感器

我们将从配置距离传感器和运行一些代码开始。以下是我们距离传感器电路的电路图:

需要进行以下连接:

  • 来自运动传感器的 VCC 连接到树莓派的 5V 直流输出

  • 树莓派的 GPIO 引脚 17 连接到距离传感器的 Trig

  • 距离传感器上的回波连接到 330 欧姆电阻

  • 距离传感器上的 GND 连接到树莓派上的 GND 和一个 470 欧姆电阻

  • 来自距离传感器回波引脚的 330 欧姆电阻的另一端连接到 470 欧姆电阻(这两个电阻创建了一个电压分压电路)

  • 来自树莓派的 GPIO 引脚 18 连接到电阻的交叉点

在这个电路中值得注意的是由两个电阻创建的电压分压器。我们使用这个分压器连接 GPIO 引脚 18。

在 Thonny 中创建一个新的 Python 文件,并将其命名为distance-sensor-test.py。输入以下代码并运行:

from gpiozero import DistanceSensor
from time import sleep

distance_sensor = DistanceSensor(echo=18, trigger=17)
while True:
    print('Distance: ', distance_sensor.distance*100)
    sleep(1)

当您将手或其他物体放在距离传感器前时,Shell 中打印的值应该会发生变化,如下所示:

确保将距离传感器放在稳固的、不动的表面上,比如面包板。

将“Hello LED”提升到另一个水平

我们最初的“Hello LED!”系统是一个简单的电路,涉及制作一个 LED,连接到 GPIO 端口,闪烁开关。自从创建该电路以来,我们已经涵盖了更多内容。我们将利用我们所学到的知识创建一个新的Hello LED电路。通过这个电路,我们将创建一个报警系统,LED 的闪烁频率表示报警距离。

以下是我们新的Hello LED系统的电路图:

这可能看起来有点复杂,线路到处都是;然而,这是一个非常简单的电路。距离传感器部分与以前一样。对于其他组件,连接如下:

  • PIR 传感器的正极连接到面包板上的 5V 直流电源

  • PIR 传感器的负极连接到面包板上的 GND

  • PIR 传感器的信号引脚连接到 GPIO 引脚 4

  • LED 的正极通过 220 欧姆电阻连接到 GPIO 引脚 21

  • LED 的负极连接到面包板上的 GND

在 Thonny 中创建一个新的 Python 文件,并将其命名为hello-led.py。输入以下代码并运行:

from gpiozero import DistanceSensor
from gpiozero import MotionSensor
from gpiozero import LED
from time import sleep

distance_sensor = DistanceSensor(echo=18, trigger=17)
motion_sensor = MotionSensor(4)
led = LED(21)

while True:  
    if(motion_sensor.motion_detected):
        blink_time=distance_sensor.distance
        led.blink(blink_time,blink_time,None,True)
    sleep(2)

LED 应该在检测到运动后立即开始闪烁。当您将手靠近距离传感器时,LED 的闪烁频率会加快。

总结

现在我们应该非常熟悉与传感器和树莓派的交互。这一章应该被视为使用我们的树莓派轻松创建感官电路的练习。

我们将在第九章中使用这些知识,构建家庭安全仪表板,在那里我们将创建一个物联网家庭安全仪表板。

问题

  1. 主动蜂鸣器和被动蜂鸣器有什么区别?

  2. 真或假?我们检查button.is_pressed参数来确认我们的按钮是否被按下。

  3. 真或假?我们需要一个电压分压电路才能连接我们的 PIR 传感器。

  4. 我们可以使用哪三种不同的方法让我们的主动蜂鸣器发出蜂鸣声?

  5. 真或假?按键必须直接连接到电路才能发挥作用。

  6. 我们使用哪个DistanceSensor参数来检查物体与距离传感器的距离?

  7. 我们使用 Sense HAT 模拟器的哪个方法来将像素打印到屏幕上?

  8. 我们如何设置我们的MotionSensor来从 GPIO 引脚 4 读取?

  9. 真或假?基本的报警系统对于我们的树莓派来说太复杂了。

  10. 真或假?Sense HAT 模拟器可以用来与连接到 GPIO 的外部传感器进行交互。

进一步阅读

请参阅 GPIO Zero 文档gpiozero.readthedocs.io/en/stable/,了解如何使用这个库的更多信息。

第九章:构建家庭安全仪表板

在第七章中,设置树莓派 Web 服务器,我们介绍了 web 框架 CherryPy。使用 CherryPy,我们可以将树莓派变成一个 Web 服务器。在第八章中,使用 Python 读取树莓派 GPIO 传感器数据,我们学会了如何从 GPIO 读取传感器数据。

在本章中,我们将从前两章学到的经验中创建一个家庭安全仪表板。

本章将涵盖以下主题:

  • 使用 CherryPy 创建我们的仪表板

  • 在我们的仪表板上显示传感器数据

完成本章所需的知识

读者需要对 Python 编程语言有一定的了解才能完成本章。还需要基本了解 HTML,包括 CSS。

项目概述

在本章中,我们将构建两个不同的家庭安全仪表板。第一个将涉及使用温度和湿度传感器,下一个将涉及使用有源蜂鸣器。

这个项目应该需要几个小时才能完成。

入门

要完成此项目,需要以下内容:

  • 树莓派 3 型(2015 年型号或更新型号)

  • 一个 USB 电源适配器

  • 一个计算机显示器

  • 一个 USB 键盘

  • USB 鼠标

  • 一个面包板

  • DHT11 温度传感器

  • 一个锁存按钮、开关或键开关

  • 一个 PIR 传感器

  • 一个有源蜂鸣器

  • 树莓派摄像头模块

使用 CherryPy 创建我们的仪表板

为了创建我们的家庭安全仪表板,我们将修改我们在第七章中编写的代码,设置树莓派 Web 服务器。这些修改包括添加来自 GPIO 的传感器数据——这是我们在第八章结束时变得非常擅长的事情,使用 Python 读取树莓派 GPIO 传感器数据

两个输入,温度和湿度传感器以及树莓派摄像头,将需要额外的步骤,以便我们可以将它们整合到我们的仪表板中。

使用 DHT11 查找温度和湿度

DHT11 温度和湿度传感器是一种低成本的业余级传感器,能够提供基本的测量。DHT11 有两种不同的版本,四针模型和三针模型。

我们将在我们的项目中使用三针模型(请参阅以下图片):

我们将使用Adafruit DHT库来读取 DHT11 数据,该库在 Raspbian 上没有预安装(截至撰写时)。要安装它,我们将克隆库的 GitHub 项目并从源代码构建它。

打开终端窗口,输入以下命令使用git并下载源代码(撰写时,git已预装在 Raspbian 中):

git clone https://github.com/adafruit/Adafruit_Python_DHT.git

您应该看到代码下载的进度。现在,使用以下命令更改目录:

cd Adafruit_Python_DHT

您将在源代码目录中。

使用以下命令构建项目:

sudo python3 setup.py install

您应该在终端中看到显示的进度:

如果您没有收到任何错误,Adafruit DHT库现在应该已安装在您的树莓派上。要验证这一点,打开 Thonny 并检查包:

现在,让我们连接电路。将 DHT11 传感器连接到树莓派如下:

  • DHT11 的 GND 连接到树莓派的 GND

  • DHT11 的 VCC 连接到树莓派的 5V DC

  • DHT11 的信号连接到 GPIO 引脚 19

有关更多信息,请参阅以下 Fritzing 图表:

一旦 DHT11 连接好,就是写一些代码的时候了:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 打开 Thonny

  2. 点击“新建”创建一个新文件

  3. 在文件中输入以下内容:

import Adafruit_DHT

dht_sensor = Adafruit_DHT.DHT11
pin = 19
humidity, temperature = Adafruit_DHT.read_retry(dht_sensor, pin)

print(humidity)
print(temperature)
  1. 将文件保存为dht-test.py

  2. 运行代码

  3. 您应该看到类似以下的内容:

让我们看看代码。我们将从导入Adafruit_DHT库开始。然后我们创建一个新的DHT11对象,并将其命名为dht_sensor湿度温度是从Adafruit_DHT类的read_retry方法中设置的。

然后我们在 shell 中打印出湿度温度的值。

使用 Pi 相机拍照

在第三章中,使用 GPIO 连接到外部世界,我们尝试了特殊的树莓派相机模块,并编写了代码来打开相机预览。是时候把相机投入使用了。

通过 CSI 相机端口将树莓派相机模块安装到树莓派上(如果尚未启用,请确保在树莓派配置屏幕中启用相机)。让我们写一些代码:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 单击“新建”以创建新文件

  3. 在文件中输入以下内容:

from picamera import PiCamera
from time import sleep

pi_cam = PiCamera()

pi_cam.start_preview()
sleep(5)
pi_cam.capture('/home/pi/myimage.png')
pi_cam.stop
  1. 将文件保存为pi-camera-test.py

  2. 运行代码

该程序导入PiCamera并在创建一个名为pi_cam的新PiCamera对象之前休眠。start_preview方法向我们显示相机在全屏中看到的内容。

捕获方法创建一个名为myimage.png的新图像文件,并将其存储在默认目录/home/pi中。

我们有 5 秒的时间来调整相机的位置,然后拍照。

以下是使用树莓派相机拍摄的我的工作区的照片:

使用 CherryPy 创建我们的仪表板

在第七章中,设置树莓派 Web 服务器,我们使用 Bootstrap 框架和WeatherDashboardHTML.py文件创建了一个天气仪表板。我们将重新访问该代码,并修改为我们的家庭安全仪表板。

要创建我们的家庭安全仪表板,请执行以下操作:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 单击“新建”以创建新文件

  3. 在文件中输入以下内容:

import cherrypy
from SecurityData import SecurityData

class SecurityDashboard:

    def __init__(self, securityData):
        self.securityData = securityData

    @cherrypy.expose
    def index(self):
        return """
               <!DOCTYPE html>
                <html lang="en">

                <head>
                    <title>Home Security Dashboard</title>
                    <meta charset="utf-8">
                    <meta name="viewport"
                        content="width=device-width,
                        initial-scale=1">

                    <meta http-equiv="refresh" content="30">

                    <link rel="stylesheet"         
                        href="https://maxcdn.bootstrapcdn.com
                        /bootstrap/4.1.0/css/bootstrap.min.css">

                    <link rel="stylesheet" href="led.css">

                    <script src="https://ajax.googleapis.com
                        /ajax/libs/jquery/3.3.1/jquery.min.js">                
                    </script>

                    <script src="https://cdnjs.cloudflare.com
                        /ajax/libs/popper.js/1.14.0
                        /umd/popper.min.js">
                    </script>

                    <script src="https://maxcdn.bootstrapcdn.com
                        /bootstrap/4.1.0/js/bootstrap.min.js">
                    </script>

                    <style>
                        .element-box {
                            border-radius: 10px;
                            border: 2px solid #C8C8C8;
                            padding: 20px;
                        }

                        .card {
                            width: 600px;
                        }

                        .col {
                            margin: 10px;
                        }
                    </style>
                </head>

                <body>
                    <div class="container">
                        <br/>
                        <div class="card">
                             <div class="card-header">
                                <h3>Home Security Dashboard</h3>
                             </div>
                             <div class="card-body">
                                <div class="row">
                                    <div class="col element-box">
                                        <h6>Armed</h6>
                                        <div class = """ +     
                                            self.securityData
                                            .getArmedStatus() + 
                                        """>
                                        </div>
                                    </div>
                                    <div class="col element-box">
                                        <h6>Temp / Humidity</h6>
                                        <p>""" + self.securityData
                                            .getRoomConditions() 
                                        + """</p>
                                    </div>
                                    <div class="col element-box">
                                        <h6>Last Check:</h6>
                                        <p>""" + self
                                            .securityData.getTime() 
                                         + """</p>
                                    </div>
                                </div>
                            </div>
                            <div class="card-footer" 
                                       align="center">

                                <img src=""" + self.securityData
                                    .getSecurityImage() + """/>
                                <p>""" + self.securityData
                                    .getDetectedMessage() + """</p>
                            </div>
                        </div>
                    </div>
                </body>

                </html>
               """

if __name__=="__main__":
    securityData = SecurityData()
    conf = {
        '/led.css':{
            'tools.staticfile.on': True,
            'tools.staticfile.filename': '/home/pi/styles/led.css'
            },
        '/intruder.png':{
            'tools.staticfile.on': True,
            'tools.staticfile.filename':                            
                '/home/pi/images/intruder.png'
            },
        '/all-clear.png':{
            'tools.staticfile.on': True,
            'tools.staticfile.filename': '/home/pi/images
                /all-clear.png'
            },
        '/not-armed.png':{
            'tools.staticfile.on': True,
            'tools.staticfile.filename': '/home/pi
                /images/not-armed.png'
            }
    }
    cherrypy.quickstart(SecurityDashboard(securityData),'/',conf)
  1. 将文件保存为security-dashboard.py

尚未运行代码,因为我们还需要创建SecurityData类。

正如您所看到的,我们对WeatherDashboardHTML.py进行了一些更改,以创建security-dashboard.py。在运行代码之前,让我们指出一些更改。

最明显的变化是使用了SecurityData类。可以想象,这个类将用于获取我们仪表板的数据:

from SecurityData import SecurityData

我们使用以下行来每 30 秒自动刷新我们的页面(我们没有自动刷新我们的天气仪表板,因为天气数据不经常变化):

<meta http-equiv="refresh" content="30">

对于我们的家庭安全仪表板,我们使用一些 CSS 魔术来表示闪烁的 LED。这是通过添加led.css文件来实现的:

<link rel="stylesheet" href="led.css">

对于数据字段,我们将从我们的SecurityData对象中访问方法。我们将在接下来的部分详细介绍这些方法。对于我们的主要部分,我们将创建一个名为conf的字典:

if __name__=="__main__":
    securityData = SecurityData()
    conf = {
        '/led.css':{
            'tools.staticfile.on': True,
            'tools.staticfile.filename': '/home/pi/styles/led.css'
            },
        '/intruder.png':{
            'tools.staticfile.on': True,
            'tools.staticfile.filename':                            
                '/home/pi/images/intruder.png'
            },
        '/all-clear.png':{
            'tools.staticfile.on': True,
            'tools.staticfile.filename': '/home/pi/images
                /all-clear.png'
            },
        '/not-armed.png':{
            'tools.staticfile.on': True,
            'tools.staticfile.filename': '/home/pi
                /images/not-armed.png'
            }
    }
    cherrypy.quickstart(SecurityDashboard(securityData),'/',conf)

我们使用conf字典将配置数据传递给cherrypy quickstart方法。此配置数据允许我们在 CherryPy 服务器中使用静态文件led.cssintruder.pngall-clear.pngnot-armed.png

先前提到了 CSS 文件led.css。其他三个文件是我们仪表板中使用的自描述图像。

为了在 CherryPy 中使用静态文件或目录,您必须创建并传递配置信息。配置信息必须包含绝对路径(而不是相对路径)。

配置信息说明 CSS 和图像文件分别位于名为stylesimages的目录中。这些目录都位于/home/pi目录中。

以下是images目录中文件的屏幕截图(请确保将文件放在正确的目录中):

在我们的仪表板上显示传感器数据

为了提供我们的仪表板数据,我们将创建一个名为SecurityData.py的新 Python 文件,我们将在其中存储SecurityData类。在这之前,让我们先建立我们的电路。

带有温度传感器的家庭安全仪表板

我们将使用 DHT11 温湿度传感器、PIR 传感器和一个 latching 按钮(或钥匙开关)来构建家庭安全仪表板的第一个版本。以下是我们家庭安全仪表板的 Fritzing 图表:

电路连接如下:

  • DHT11 的 GND 连接到 GND

  • DHT11 的 VCC 连接到 5V 直流电源

  • DHT11 的信号连接到 GPIO 引脚 19

  • PIR 传感器的 GND 连接到 GND

  • PIR 传感器的 VCC 连接到 5V 直流电源

  • PIR 传感器的信号连接到 GPIO 引脚 4

  • 拉 atching 按钮的一端连接到 GPIO 引脚 8

  • 拉 atching 按钮的另一端接地

  • 树莓派摄像头模块连接到 CSI 端口(未显示)

以下是我们电路的照片。需要注意的一点是我们为 DHT11 传感器使用了单独的面包板(更容易放在微型面包板上),以及钥匙开关代替 latching 按钮:

现在是时候编写代码了:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 点击“新建”创建一个新文件

  3. 将以下内容输入文件:

from gpiozero import MotionSensor
from gpiozero import Button
from datetime import datetime
from picamera import PiCamera
import Adafruit_DHT

class SecurityData:
    humidity=''
    temperature=''
    detected_message=''

    dht_pin = 19
    dht_sensor = Adafruit_DHT.DHT11
    switch = Button(8)
    motion_sensor = MotionSensor(4)
    pi_cam = PiCamera()

    def getRoomConditions(self):
        humidity, temperature = Adafruit_DHT
            .read_retry(self.dht_sensor, self.dht_pin)

        return str(temperature) + 'C / ' + str(humidity) + '%'

    def getDetectedMessage(self):
        return self.detected_message

    def getArmedStatus(self):
        if self.switch.is_pressed:
            return "on"
        else:
            return "off"

    def getSecurityImage(self):

        if not(self.switch.is_pressed):
            self.detected_message = ''
            return "/not-armed.png"

        elif self.motion_sensor.motion_detected:
            self.pi_cam.resolution = (500, 375)
            self.pi_cam.capture("/home/pi/images/intruder.png")
            self.detected_message = "Detected at: " + 
                self.getTime()
            return "/intruder.png"

        else:
            self.detected_message = ''
            return "/all-clear.png"

    def getTime(self):
        return datetime.now().strftime('%Y-%m-%d %H:%M:%S')

if __name__ == "__main__":

    while True:
        security_data = SecurityData()
        print(security_data.getRoomConditions())
        print(security_data.getArmedStatus())
        print(security_data.getTime())
  1. 将文件保存为SecurityData.py

  2. 运行代码

您应该在 shell 中得到一个输出,指示房间中的温度湿度水平,一个表示开关位置的onoff,以及当前时间。尝试打开和关闭开关,看看输出是否发生变化。

在运行仪表板代码(security-dashboard.py)之前,让我们先回顾一下SecurityData类。正如我们所看到的,代码的第一部分是我们已经熟悉的标准样板代码。getRoomConditionsgetDetectedMessage方法要么是不言自明的,要么是我们已经讨论过的内容。

我们的getArmedStatus方法做了一个小技巧,以保持我们的代码简单而紧凑:

def getArmedStatus(self):
    if self.switch.is_pressed:
        return "on"
    else:
        return "off"

我们可以看到getArmedStatus返回的是onoff,而不是大多数具有二进制返回的方法返回的TrueFalse。我们这样做是为了我们仪表板代码的武装部分。

以下是SecurityDashboard类的index方法生成的 HTML 代码:

<div class="col element-box">
    <h6>Armed</h6>
    <div class = """ + self.securityData.getArmedStatus() + """>
    </div>
</div>

正如我们所看到的,getArmedStatus方法在构建 div 标签时被调用,以替代 CSS 类名。单词onoff指的是我们led.css文件中的 CSS 类。当返回on时,我们得到一个闪烁的红色 LED 类型图形。当返回off时,我们得到一个黑点。

因此,拉 atching 开关(或钥匙开关)的位置决定了 div 标签是否具有 CSS 类名on或 CSS 类名off,通过SecurityData类的getArmedStatus方法。

我们的代码在getSecurityImage方法中变得非常有趣:

def getSecurityImage(self):

        if not(self.switch.is_pressed):
            self.detected_message = ''
            return "/not-armed.png"

        elif self.motion_sensor.motion_detected:
            self.pi_cam.resolution = (500, 375)
            self.pi_cam.capture("/home/pi/images/intruder.png")
            self.detected_message = "Detected at: " + 
                self.getTime()
            return "/intruder.png"

        else:
            self.detected_message = ''
            return "/all-clear.png"

我们的第一个条件语句检查电路是否处于武装状态(开关处于on位置)。如果没有武装,那么我们只需要将检测到的消息设置为空,并返回对not-armed.png文件的引用(/not-armed.png在我们在security-dashboard.py文件中设置的配置信息中定义)。

如果我们看一下SecurityDashboard类(security-dashboard.py文件)中的代码,我们可以看到getSecurityImage方法在生成的 HTML 代码的底部附近被调用:

<div class="card-footer" align="center">
    <img src=""" + self.securityData.getSecurityImage() + """/>
    <p>""" + self.securityData.getDetectedMessage() + """</p>
</div>

如果电路中的开关没有打开,我们将在仪表板页脚看到以下内容,后面没有描述(空的detected_message值):

我们代码中的第二个条件语句是在开关处于on并且检测到运动时触发的。在这种情况下,我们设置我们树莓派摄像头的分辨率,然后拍照。

我们可能在类的实例化过程中设置了树莓派摄像头的分辨率,这可能更有意义。但是,将这行放在这里使得在完成代码之前调整分辨率更容易,因为这行存在于我们关注的方法中。

我们将文件命名为intruder.png,并将其存储在security-dashboard.py文件中的配置代码可以找到的位置。

我们还根据当前时间创建了一个detected_message值。这条消息将为我们从树莓派摄像头获取的图像提供时间戳。

最后的else:语句是我们返回/all-clear.png的地方。到达这一点时,我们知道开关是“开启”的,并且没有检测到任何运动。我们在仪表板页脚将看到以下图像:

NOT ARMED消息一样,在ALL CLEAR后面不会有描述。只有当开关处于“开启”状态且 PIR 传感器没有检测到任何运动(motion_detectedfalse)时,我们才会看到这个图形。

现在,让我们运行仪表板代码。如果您还没有这样做,请点击红色按钮停止SecurityData程序。点击security-dashboard.py文件的选项卡,然后点击运行。等待几秒钟,以便让 CherryPy 运行起来。

打开一个网络浏览器,然后导航到以下地址:

http://127.0.0.1:8080

将开关置于“关闭”位置,您应该看到以下仪表板屏幕:

正如我们所看到的,武装部分下的 LED 是黑色的,在页脚中会得到一个NOT ARMED消息。我们还可以看到temperaturehumidity的显示,即使系统没有武装。

最后一个复选框显示了代码上次检查开关状态的时间。如果你等待 30 秒,你应该看到页面刷新并显示相同的信息。

现在,打开开关,站在一边,这样 PIR 传感器就不会检测到你。你应该看到一个类似于以下的屏幕:

您会注意到武装部分的 LED 现在变成了闪烁的红色,temperaturehumidity读数要么相同,要么略有不同,上次检查已更新到当前时间,并且页脚中出现了ALL CLEAR消息。

让我们看看是否能抓住入侵者。将树莓派摄像头对准门口,等待 PIR 传感器触发:

看来我们已经抓到了入侵者!

具有快速响应的家庭安全仪表板

您可能已经注意到我们的页面刷新需要很长时间。当然,这是由于 30 秒的刷新时间,以及 DHT11 读取数值所需的长时间。

让我们改变我们的代码,使其更快,并给它一个蜂鸣器来吓跑入侵者。

用连接到 GPIO 引脚 17 的蜂鸣器替换 DHT11(对于这个简单的更改,我们不需要 Fritzing 图)。

我们将首先创建SecurityDataQuick数据类:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 点击“新建”以创建一个新文件

  3. 在文件中键入以下内容:

from gpiozero import MotionSensor
from gpiozero import Button
from datetime import datetime
from picamera import PiCamera
from gpiozero import Buzzer
from time import sleep

class SecurityData:
    alarm_status=''
    detected_message=''

    switch = Button(8)
    motion_sensor = MotionSensor(4)
    pi_cam = PiCamera()
    buzzer = Buzzer(17)

    def sound_alarm(self):
        self.buzzer.beep(0.5,0.5, 5, True)
        sleep(1)

    def getAlarmStatus(self):

        if not(self.switch.is_pressed):
            self.alarm_status = 'not-armed'
            return "Not Armed"

        elif self.motion_sensor.motion_detected:
            self.alarm_status = 'motion-detected'
            self.sound_alarm()
            return "Motion Detected"

        else:
            self.alarm_status = 'all-clear'
            return "All Clear"

    def getDetectedMessage(self):
        return self.detected_message

    def getArmedStatus(self):
        if self.switch.is_pressed:
            return "on"
        else:
            return "off"

    def getSecurityImage(self):

        if self.alarm_status=='not-armed':
            self.detected_message = ''
            return "/not-armed.png"

        elif self.alarm_status=='motion-detected':
            self.pi_cam.resolution = (500, 375)
            self.pi_cam.capture("/home/pi/images/intruder.png")

            self.detected_message = "Detected at: " + 
                self.getTime()

            return "/intruder.png"

        else:
            self.detected_message = ''
            return "/all-clear.png"

    def getTime(self):
        return datetime.now().strftime('%Y-%m-%d %H:%M:%S')

if __name__ == "__main__":

    while True:
        security_data = SecurityData()
        print(security_data.getArmedStatus())
        print(security_data.getTime())

  1. 将文件保存为SecurityDataQuick.py

  2. 运行代码

在我们的 shell 中,我们应该看到开关和当前时间的值。通过点击红色按钮停止程序。

正如我们所看到的,已经发生了一些变化。我们没有做的一个变化是更改类名。将其保持为SecurityData意味着以后对我们的仪表板代码的更改更少。

我们添加了GPIO Zero蜂鸣器的库,并删除了与 DHT11 传感器相关的任何代码。我们还创建了一个名为sound_buzzer的新方法,当检测到入侵者时我们将调用它。

添加了一个名为alarm_status的新变量,以及相应的getAlarmStatus方法。我们将类的核心逻辑移到了这个方法中(远离getSecurityImage),因为在这里我们检查开关和 PIR 传感器的状态。变量alarm_status在其他地方用于确定是否要拍照。如果检测到入侵者,我们还会在这个方法中发出警报。

通过添加新方法,我们更改了getSecurityImage。通过在getSecurityImage方法中使用alarm_status,我们无需检查传感器的状态。现在我们可以将getSecurityImage用于其预期用途——在检测到入侵者时拍照。

现在是时候更改仪表板代码了:

  1. 从应用程序菜单|编程|Thonny Python IDE 打开 Thonny

  2. 单击“新建”以创建新文件

  3. 在文件中输入以下内容:

import cherrypy
from SecurityDataQuick import SecurityData

class SecurityDashboard:

def __init__(self, securityData):
    self.securityData = securityData

@cherrypy.expose
def index(self):
    return """
        <!DOCTYPE html>
        <html lang="en">

        <head>
            <title>Home Security Dashboard</title>
            <meta charset="utf-8">

            <meta name="viewport" content="width=device-
        width, initial-scale=1">

            <meta http-equiv="refresh" content="2">

            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com
        /bootstrap/4.1.0/css/bootstrap.min.css">

            <link rel="stylesheet" href="led.css">

            <script src="https://ajax.googleapis.com
        /ajax/libs/jquery/3.3.1/jquery.min.js">
            </script>

            <script src="https://cdnjs.cloudflare.com
        /ajax/libs/popper.js/1.14.0
        /umd/popper.min.js">
            </script>

            <script src="https://maxcdn.bootstrapcdn.com
        /bootstrap/4.1.0/js/bootstrap.min.js">
            </script>

            <style>
                .element-box {
                    border-radius: 10px;
                    border: 2px solid #C8C8C8;
                    padding: 20px;
                }

                .card {
                    width: 600px;
                }

                .col {
                    margin: 10px;
                }
            </style>
        </head>

        <body>
            <div class="container">
                <br />
                <div class="card">
                    <div class="card-header">
                        <h3>Home Security Dashboard</h3>
                    </div>
                    <div class="card-body">
                        <div class="row">
                            <div class="col element-box">
                                <h4>Armed</h4>

                                <div class=""" + self
        .securityData
        .getArmedStatus() 
        + """>
                                </div>
                            </div>

                            <div class="col element-box">
                                <h4>Status</h4>
                                <p>""" + self.securityData
                                    .getAlarmStatus()
                                    + """</p>
                            </div>

                            <div class="col element-box">
                                <h4>Last Check:</h4>

                                <p>""" + self.securityData
                                    .getTime() + """
                                </p>
                            </div>
                        </div>
                    </div>
                    <div class="card-footer" align="center">
                        <img src=""" + self.securityData
        .getSecurityImage() + """ />
                        <p>""" + self.securityData
                            .getDetectedMessage() + """</p>
                    </div>
                </div>
            </div>
        </body>

        </html>
    """

if __name__=="__main__":
    securityData = SecurityData()
    conf = {
        '/led.css':{
        'tools.staticfile.on': True,
        'tools.staticfile.filename': '/home/pi/styles/led.css'
        },
        '/intruder.png':{
        'tools.staticfile.on': True,
        'tools.staticfile.filename': '/home/pi
        /images/intruder.png'
        },
        '/all-clear.png':{
        'tools.staticfile.on': True,
        'tools.staticfile.filename': '/home/pi
        /images/all-clear.png'
        },
        '/not-armed.png':{
        'tools.staticfile.on': True,
        'tools.staticfile.filename': '/home/pi
        /images/not-armed.png'
        }
    }
    cherrypy.quickstart(SecurityDashboard(securityData),'/',conf)

  1. 将文件保存为SecurityDataQuick.py

  2. 运行代码

  3. 返回到您的网络浏览器并刷新仪表板页面

我们的仪表板现在应该与以下截图匹配:

我们的仪表板应该每两秒刷新一次,而不是 30 秒,当处于武装模式时检测到运动时应该发出蜂鸣器声音。

让我们看看代码。我们仪表板的更改相当容易理解。但值得注意的是我们仪表板上中间框的更改:

<div class="col element-box">
    <h4>Status</h4>
    <p>""" + self.securityData.getAlarmStatus() + """</p>
</div>

我们通过getAlarmStatus方法将房间的温度湿度替换为开关和 PIR 传感器的状态。通过这种更改,我们可以使用getAlarmStatus方法作为我们的初始化方法,其中我们设置SecurityData类变量alarm_status的状态。

如果我们真的想要一丝不苟,我们可以更改我们的代码,以便使用开关和 PIR 传感器的值来初始化SecurityData类。目前,SecurityData更像是一种实用类,其中必须先调用某些方法。我们暂且放过它。

摘要

正如我们所看到的,使用树莓派构建安全应用程序非常容易。尽管我们正在查看我们的仪表板并在同一台树莓派上托管我们的传感器,但将树莓派设置为向网络中的其他计算机(甚至是互联网)提供仪表板并不太困难。在第十章中,发布到 Web 服务,我们将与

将传感器数据进一步处理并发布到互联网。

问题

  1. 真或假?DHT11 传感器是一种昂贵且高精度的温湿度传感器。

  2. 真或假?DHT11 传感器可以检测到来自太阳的紫外线。

  3. 真或假?运行 DHT11 所需的代码已预装在 Raspbian 中。

  4. 如何设置 Pi 摄像头模块的分辨率?

  5. 如何设置 CherryPy 以便可以访问本地静态文件?

  6. 如何设置网页的自动刷新?

  7. 真或假?通过使用 CSS,我们可以模拟闪烁的 LED。

  8. SecurityData类的目的是什么?

  9. 我们找到了谁或什么作为我们的入侵者?

  10. 如果我们想要一丝不苟,我们将如何更改我们的SecurityData类?

进一步阅读

我们代码中使用的刷新方法很有效,但有点笨拙。我们的仪表板可以通过使用 AJAX 代码进行改进,其中字段被更新但页面不更新。请查阅 CherryPy 文档以获取更多信息。

第十章:发布到 Web 服务

在物联网的核心是允许与物理设备交互的 Web 服务。在本章中,我们将探讨使用 Web 服务来显示来自树莓派的传感器数据的用途。我们还将研究 Twilio,一个短信服务,以及我们如何使用此服务从树莓派发送短信给自己。

本章将涵盖以下主题:

  • 将传感器数据发布到基于云的服务

  • 为文本消息传输设置账户

项目概述

在本章中,我们将编写代码将我们的传感器数据显示到 IoT 仪表板上。此外,我们还将探索 Twilio,一个短信服务。然后,我们将把这两个概念结合起来,以增强我们在第九章中构建的家庭安全仪表板。

入门

要完成此项目,需要以下内容:

  • 树莓派 3 型号(2015 年或更新型号)

  • 一个 USB 电源适配器

  • 一个计算机显示器

  • 一个 USB 键盘

  • 一个 USB 鼠标

  • 一个面包板

  • 跳线

  • 一个 DHT-11 温度传感器

  • 一个 PIR 传感器

  • 一个按钮(锁定)

  • 一个按键开关(可选)

将传感器数据发布到基于云的服务

在本节中,我们将使用 MQTT 协议将传感器数据发布到在线仪表板。这将涉及在 ThingsBoard 网站上设置一个账户,并使用demo环境。

安装 MQTT 库

我们将使用 MQTT 协议与 ThingsBoard 仪表板进行通信。要在树莓派上设置库,请执行以下操作:

  1. 从主工具栏打开终端设备

  2. 输入**sudo pip3 install pho-mqtt**

  3. 您应该看到库已安装

设置一个账户并创建一个设备

首先,转到 ThingsBoard 网站www.thingsboard.io

  1. 点击屏幕顶部的 TRY IT NOW 按钮。向下滚动并在 Thing Board Community Edition 部分下点击 LIVE DEMO 按钮:

  1. 您将看到一个注册窗口。输入适当的信息设置一个账户。一旦您的账户成功设置,您将看到一个对话框显示以下内容:

  1. 点击登录进入应用程序。之后,您应该在屏幕左侧看到一个菜单:

  1. 点击 DEVICES。在屏幕右下角,找到一个带加号的圆形橙色图形,如下所示:

  1. 点击这个橙色圆圈添加一个新设备。在添加设备对话框中,输入Room Conditions作为名称,并选择默认作为设备类型。不要选择 Is gateway。点击 ADD:

  1. 您应该在您的设备下看到一个新的框,名称为 Room Conditions:

  1. 点击此框,然后会从右侧滑出一个菜单。点击 COPY ACCESS TOKEN 按钮将此令牌复制到剪贴板上:

我们在这里做的是设置 ThingsBoard 账户和 ThingsBoard 内的新设备。我们将使用此设备从树莓派检索传感信息,并制作这些值的仪表板。

读取传感器数据并发布到 ThingsBoard

现在是时候创建我们的电路和代码了。使用 GPIO 引脚 19 安装 DHT-11 传感器(如果不确定如何将 DHT-11 传感器连接到树莓派,请参考第九章,构建家庭安全仪表板):

  1. 打开 Thonny 并创建一个名为dht11-mqtt.py的新文件。在文件中输入以下内容并运行。确保粘贴从剪贴板中复制的访问令牌:
from time import sleep
import Adafruit_DHT
import paho.mqtt.client as mqtt
import json

host = 'demo.thingsboard.io'
access_token = '<<access token>>'
dht_sensor = Adafruit_DHT.DHT11
pin = 19

sensor_data = {'temperature': 0, 'humidity': 0}

client = mqtt.Client()
client.username_pw_set(access_token)

while True:
 humidity, temperature = Adafruit_DHT
 .read_retry(dht_sensor, pin)

 print(u"Temperature: {:g}\u00b0C, Humidity
 {:g}%".format(temperature, humidity))

 sensor_data['temperature'] = temperature
 sensor_data['humidity'] = humidity
 client.connect(host, 1883, 20)
 client.publish('v1/devices/me/telemetry', 
 json.dumps(sensor_data), 1)
 client.disconnect()
 sleep(10)
  1. 您应该在 shell 中看到类似以下截图的输出:

  1. 每 10 秒应该有一个新行。正如您所看到的,房间里又热又潮。

让我们更仔细地看一下前面的代码:

  1. 我们的import语句让我们可以访问代码所需的模块:
from time import sleep
import Adafruit_DHT
import paho.mqtt.client as mqtt
import json

我们已经熟悉了sleepAdafruit_DHTjsonPaho MQTT库让我们可以访问client对象,我们将使用它来将我们的传感器数据发布到仪表板。

  1. 代码中的接下来两行用于设置demo服务器的 URL 和我们之前从设备检索到的访问令牌的变量。我们需要这两个值才能连接到 MQTT 服务器并发布我们的传感器数据:
host = 'demo.thingsboard.io'
access_token = '<<access token>>'
  1. 我们将dht_sensor变量定义为Adafruit库中的DHT11对象。我们使用传感器的引脚19
dht_sensor = Adafruit_DHT.DHT11
pin = 19
  1. 然后我们定义一个dictionary对象来存储将发布到 MQTT 服务器的传感器数据:
sensor_data = {'temperature': 0, 'humidity': 0}
  1. 然后我们创建一个mqtt Client类型的client对象。用户名和密码使用代码中先前定义的access_token设置:
client = mqtt.Client()
client.username_pw_set(access_token)
  1. 连续的while循环包含读取传感器数据的代码,然后将其发布到 MQTT 服务器。通过从read_retry方法读取湿度和温度,并将相应的sensor_data字典值设置如下:
while True:
    humidity, temperature = Adafruit_DHT
                                .read_retry(dht_sensor, pin)

    print(u"Temperature: {:g}\u00b0C, Humidity
               {:g}%".format(temperature, humidity))

    sensor_data['temperature'] = temperature
    sensor_data['humidity'] = humidity
  1. 以下client代码是负责将我们的传感器数据发布到 MQTT 服务器的代码。我们使用client对象的connect方法连接,传入主机值、端口(默认端口)和20秒的保持活动时间。与许多 MQTT 示例不同,我们不创建循环并寻找回调,因为我们只对发布传感器值感兴趣,而不是订阅主题。在这种情况下,我们要发布的主题是v1/devices/me/telemetry,如 ThingsBoard 文档示例代码所示。然后我们断开与client的连接:
client.connect(host, 1883, 20)
client.publish('v1/devices/me/telemetry', 
            json.dumps(sensor_data), 1)
client.disconnect()
sleep(10)

我们现在将在 ThingsBoard 中创建一个仪表板,以显示从我们的代码发送的传感器值。

在 ThingsBoard 中创建仪表板

以下是将湿度值添加到仪表板的步骤:

  1. 返回 ThingsBoard,单击“设备”,然后单击“ROOM CONDITIONS”。侧边菜单应该从右侧滑出:

  1. 单击“最新遥测”选项卡。

  2. 您应该看到湿度和温度的值,以及上次更新这些值的时间。通过单击左侧的复选框选择湿度。现在,单击“在小部件上显示”:

  1. 选择当前捆绑到模拟表盘,并循环浏览表盘,直到找到湿度表盘小部件。单击“添加到仪表板”按钮:

  1. 选择创建新仪表板,并输入Room Conditions作为名称:

  1. 不要选择“打开仪表板”复选框。单击“添加”按钮。

  2. 重复上述步骤以添加温度值。选择温度小部件,并将小部件添加到“Room Conditions”仪表板。这次,在单击“添加”之前选择“打开仪表板”:

现在,您应该看到一个仪表板,其中显示了湿度和温度值,显示在模拟表盘上。

与朋友分享您的仪表板

如果您想要将此仪表板公开,以便其他人可以看到它,您需要执行以下操作:

  1. 通过单击“DASHBOARDS”导航到仪表板屏幕:

  1. 单击“使仪表板公开”选项:

  1. 您将看到对话框显示“仪表板现在是公开的”,如下截图所示。您可以复制并粘贴 URL,或通过社交媒体分享:

设置用于文本消息传输的账户

在本节中,我们将连接到一个文本消息传输服务,并从树莓派向我们的手机发送一条短信。我们将利用这些信息以及我们迄今为止关于发布感知信息的所学知识,来创建一个增强版的安全仪表板,位于第九章,“构建家庭安全仪表板”中。

设置 Twilio 账户

Twilio 是一个服务,它为软件开发人员提供通过其网络服务 API 来编程创建和接收文本和电话通话的能力。让我们从设置 Twilio 账户开始:

  1. 在网页浏览器中,导航至 www.twilio.com

  2. 点击页面右上角的红色注册按钮

  3. 输入适当的个人信息和密码,然后选择短信、到达提醒和 Python 作为密码下面的字段:

  1. 提供一个电话号码,以便通过短信接收授权码,如下所示:

  1. 输入您收到的授权码,如下所示:

  1. 下一步是为您将要使用的项目命名。我们将其命名为Doorbell。输入名称并点击“继续”:

  1. 我们需要一个账户的电话号码才能与其进行交互。点击获取号码:

  1. 将向您呈现一个号码。如果这个号码适合您,请点击“选择此号码”:

  1. 您现在已经设置好并准备使用 Twilio:

Twilio 是一个付费服务。您将获得一个初始金额来使用。请在创建应用程序之前检查使用此服务的成本。

在我们的树莓派上安装 Twilio

要从 Python 访问 Twilio,我们需要安装twilio库。打开终端并输入以下内容:

pip3 install twilio

您应该在终端中看到 Twilio 安装的进度。

通过 Twilio 发送短信

在发送短信之前,我们需要获取凭据。在您的 Twilio 账户中,点击“设置”|“常规”,然后滚动到“API 凭据”:

我们将使用 LIVE 凭据和 TEST 凭据的值。打开 Thonny 并创建一个名为twilio-test.py的新文件。在文件中输入以下代码并运行。确保粘贴 LIVE 凭据(请注意,发送短信将收取您的账户费用):

from twilio.rest import Client

account_sid = '<<your account_sid>>'
auth_token = '<<your auth_token>>'
client = Client(account_sid, auth_token)

message = client.messages.create(
                              body='Twilio says hello!',
                              from_='<<your Twilio number>>',
                              to='<<your cell phone number>>'
                          )
print(message.sid)

您应该会在您的手机上收到一条消息“Twilio 问候!”的短信。

创建一个新的家庭安全仪表板

在第九章,“构建家庭安全仪表板”中,我们使用 CherryPy 创建了一个家庭安全仪表板。物联网的强大之处在于能够构建一个连接到世界各地设备的应用程序。我们将把这个想法应用到我们的家庭安全仪表板上。如果尚未组装,请使用第九章,“构建家庭安全仪表板”中的温度传感器来构建家庭安全仪表板:

  1. 我们将通过将我们的感知数据封装在一个“类”容器中来开始我们的代码。打开 Thonny 并创建一个名为SensoryData.py的新文件:
from gpiozero import MotionSensor
import Adafruit_DHT

class SensoryData:
    humidity=''
    temperature=''
    detected_motion=''

    dht_pin = 19
    dht_sensor = Adafruit_DHT.DHT11
    motion_sensor = MotionSensor(4)

    def __init__(self):
        self.humidity, self.temperature = Adafruit_DHT
                            .read_retry(self.dht_sensor, 
                            self.dht_pin)

        self.motion_detected = self.motion_sensor.motion_detected

    def getTemperature(self):
        return self.temperature

    def getHumidity(self):
        return self.humidity

    def getMotionDetected(self):
        return self.motion_detected

if __name__ == "__main__":

    while True:
        sensory_data = SensoryData()
        print(sensory_data.getTemperature())
        print(sensory_data.getHumidity())
        print(sensory_data.getMotionDetected())

  1. 运行程序来测试我们的传感器。这里没有我们尚未涵盖的内容。基本上我们只是在测试我们的电路和传感器。您应该在 shell 中看到感知数据的打印。

  2. 现在,让我们创建我们的感知仪表板。打开 Thonny 并创建一个名为SensoryDashboard.py的新文件。代码如下:

import paho.mqtt.client as mqtt
import json
from SensoryData import SensoryData
from time import sleep

class SensoryDashboard:

    host = 'demo.thingsboard.io'
    access_token = '<<your access_token>>'
    client = mqtt.Client()
    client.username_pw_set(access_token)
    sensory_data = ''

    def __init__(self, sensoryData):
        self.sensoryData = sensoryData

    def publishSensoryData(self):
        sensor_data = {'temperature': 0, 'humidity': 0,
                        'Motion Detected':False}

        sensor_data['temperature'] =  self.sensoryData
                                        .getTemperature()

        sensor_data['humidity'] = self.sensoryData.getHumidity()

        sensor_data['Motion Detected'] = self.sensoryData
                                        .getMotionDetected()

        self.client.connect(self.host, 1883, 20)
        self.client.publish('v1/devices/me/telemetry',         
                                json.dumps(sensor_data), 1)
        self.client.disconnect()

        return sensor_data['Motion Detected']

if __name__=="__main__":

    while True:
        sensoryData = SensoryData()
        sensory_dashboard = SensoryDashboard(sensoryData)

        print("Motion Detected: " +             
                str(sensory_dashboard.publishSensoryData()))

        sleep(10)

我们在这里所做的是将以前的代码中的dht-mqtt.py文件封装在一个class容器中。我们用一个SensoryData对象来实例化我们的对象,以便从传感器获取数据。publishSensoryData()方法将感官数据发送到我们的 MQTT 仪表板。注意它如何返回运动传感器的状态?我们在主循环中使用这个返回值来打印出运动传感器的值。然而,这个返回值在我们未来的代码中会更有用。

让我们将运动传感器添加到我们的 ThingsBoard 仪表板中:

  1. 在浏览器中打开 ThingsBoard

  2. 点击设备菜单

  3. 点击房间条件设备

  4. 选择最新的遥测

  5. 选择检测到的运动值

  6. 点击小部件上的显示

  7. 在卡片下面,找到由一个大橙色方块组成的小部件,如下所示:

  1. 点击添加到仪表板

  2. 选择现有的房间条件仪表板

  3. 选中打开仪表板

  4. 点击添加

您应该看到新的小部件已添加到房间条件仪表板。通过点击页面右下角的橙色铅笔图标,您可以移动和调整小部件的大小。编辑小部件,使其看起来像以下的屏幕截图:

我们在这里所做的是重新创建第九章中的家庭安全仪表板的第一个版本,构建家庭安全仪表板,并采用了更加分布式的架构。我们不再依赖于我们的树莓派通过 CherryPy 网页提供感官信息。我们能够将我们的树莓派的角色减少到感官信息的来源。正如您所能想象的,使用多个树莓派来使用相同的仪表板非常容易。

通过靠近 PIR 传感器来测试这个新的仪表板。看看能否使检测到运动的小部件变为true

为了使我们的新家庭安全仪表板更加分布式,让我们添加在 PIR 运动传感器激活时发送文本消息的功能。打开 Thonny 并创建一个名为SecurityDashboardDist.py的新文件。以下是要插入文件的代码:

from twilio.rest import Client
from SensoryData import SensoryData
from SensoryDashboard import SensoryDashboard
from gpiozero import Button
from time import time, sleep

class SecurityDashboardDist:
    account_sid = ''
    auth_token = ''
    time_sent = 0
    test_env = True 
    switch = Button(8)

    def __init__(self, test_env = True):
        self.test_env = self.setEnvironment(test_env)

    def setEnvironment(self, test_env):
        if test_env:
            self.account_sid = '<<your Twilio test account_sid>>'
            self.auth_token = '<<your Twilio test auth_token>>'
            return True
        else:
            self.account_sid = '<<your Twilio live account_sid>>'
            self.auth_token = '<<your Twilio live auth_token>>'
            return False

    def update_dashboard(self, sensoryDashboard):
        self.sensoryDashboard = sensoryDashboard

        motion_detected = self
                          .sensoryDashboard
                          .publishSensoryData()

        if motion_detected:
            return self.send_alert()
        else:
            return 'Alarm not triggered'

    def send_alert(self):
        if self.switch.is_pressed:
            return self.sendTextMessage()
        else:
            return "Alarm triggered but Not Armed"

    def sendTextMessage(self):
        message_interval = round(time() - self.time_sent)

        if message_interval > 600:
            twilio_client = Client(self.account_sid, 
                                   self.auth_token)

            if self.test_env:
                message = twilio_client.messages.create(
                            body='Intruder Alert',
                            from_= '+15005550006',
                            to='<<your cell number>>'
                          )
            else:
                message = twilio_client.messages.create(
                            body='Intruder Alert',
                            from_= '<<your Twilio number>>',
                            to='<<your cell number>>'
                          )

            self.time_sent=round(time())

            return 'Alarm triggered and text message sent - ' 
                    + message.sid
        else:
             return 'Alarm triggered and text 
                    message sent less than 10 minutes ago'   

if __name__=="__main__":  
    security_dashboard = SecurityDashboardDist()

    while True:
        sensory_data = SensoryData()
        sensory_dashboard = SensoryDashboard(sensory_data)
        print(security_dashboard.update_dashboard(
                sensory_dashboard))

        sleep(5)

利用第九章中的家庭安全仪表板电路的第一个版本,构建家庭安全仪表板,这段代码使用钥匙开关来激活发送文本消息的呼叫,如果运动传感器检测到运动。当钥匙开关处于关闭位置时,每当运动传感器检测到运动时,您将收到一条消息,内容为警报触发但未激活

如果还没有打开,请打开钥匙开关以激活电路。通过四处移动来激活运动传感器。您应该会收到一条通知,说明已发送了一条文本消息。消息的 SID 也应该显示出来。您可能已经注意到,您实际上并没有收到一条文本消息。这是因为代码默认为 Twilio 测试环境。在我们打开实时环境之前,让我们先看一下代码。

我们首先导入我们代码所需的库:

from twilio.rest import Client
from SensoryData import SensoryData
from SensoryDashboard import SensoryDashboard
from gpiozero import Button
from time import time, sleep

这里没有太多我们以前没有见过的东西;然而,请注意SensoryDataSensoryDashboard的导入。由于我们已经封装了读取感官数据的代码,现在我们可以把它看作一个黑匣子。我们知道我们需要安全仪表板的感官数据,但我们不关心如何获取这些数据以及它将在哪里显示。SensoryData为我们提供了我们需要的感官数据,SensoryDashboard将其发送到某个仪表板。在我们的SecurityDashboardDist.py代码中,我们不必关心这些细节。

我们为我们的分布式安全仪表板创建了一个名为SecurityDashboardDist的类。重要的是要通过它们的名称来区分我们的类,并选择描述class是什么的名称。

class SecurityDashboardDist:

在声明了一些整个类都可以访问的类变量之后,我们来到了我们的类初始化方法:

    account_sid = ''
    auth_token = ''
    time_sent = 0
    test_env = True 
    switch = Button(8)

    def __init__(self, test_env = True):
        self.test_env = self.setEnvironment(test_env)

initialization方法中,我们设置了类范围的test_env变量(用于test环境)。默认值为True,这意味着我们必须有意地覆盖默认值才能运行实时仪表板。我们使用setEnvironment()方法来设置test_env

def setEnvironment(self, test_env):
        if test_env:
            self.account_sid = '<<your Twilio test account_sid>>'
            self.auth_token = '<<your Twilio test auth_token>>'
            return True
        else:
            self.account_sid = '<<your Twilio live account_sid>>'
            self.auth_token = '<<your Twilio live auth_token>>'
            return False

setEnvironment()方法根据test_env的值设置类范围的account_idauth_token值,以便设置测试环境或实际环境。基本上,我们只是通过setEnvironment()方法传回test_env的状态,同时设置我们需要启用测试或实际短信环境的变量。

update_dashboard()方法通过传入的SensoryDashboard对象调用传感器和感官仪表板。这里是我们采取的面向对象方法的美妙之处,因为我们不需要关心传感器是如何读取的或仪表板是如何更新的。我们只需要传入一个SensoryDashboard对象就可以完成这个任务。

def update_dashboard(self, sensoryDashboard):
        self.sensoryDashboard = sensoryDashboard

        motion_detected = self
                          .sensoryDashboard
                          .publishSensoryData()

        if motion_detected:
            return self.send_alert()
        else:
            return 'Alarm not triggered'

update_dashboard方法还负责确定是否发送短信,通过检查运动传感器的状态。您还记得我们在调用SensoryDashboard类的publishSensoryData()方法时返回了运动传感器的状态吗?这就是它真正方便的地方。我们可以使用这个返回值来确定是否应该发送警报。我们根本不需要在我们的类中检查运动传感器的状态,因为它可以很容易地从SensoryDashboard类中获得。

send_alert()方法检查开关的状态,以确定是否发送短信:

def send_alert(self):
        if self.switch.is_pressed:
            return self.sendTextMessage()
        else:
            return "Alarm triggered but Not Armed"

也许你会想知道为什么我们在这里检查传感器(在这种情况下是开关)的状态,而不是从SensoryDashboard类中检查。答案是?我们正在通过封装传感数据仪表板来构建家庭安全仪表板。SensorDashboard类中不需要开关,因为它不涉及从 GPIO 到 MQTT 仪表板的传感数据的读取和传输。开关是安全系统的领域;在这种情况下是SecurityDashboardDist类。

SecurityDasboardDist类的核心是sendTextMessage()方法,如下所述:

def sendTextMessage(self):
        message_interval = round(time() - self.time_sent)

        if message_interval > 600:
            twilio_client = Client(self.account_sid, 
                                   self.auth_token)

            if self.test_env:
                message = twilio_client.messages.create(
                            body='Intruder Alert',
                            from_= '+15005550006',
                            to='<<your cell number>>'
                          )
            else:
                message = twilio_client.messages.create(
                            body='Intruder Alert',
                            from_= '<<your Twilio number>>',
                            to='<<your cell number>>'
                          )

            self.time_sent=round(time())

            return 'Alarm triggered and text message sent - ' 
                    + message.sid
        else:
             return 'Alarm triggered and text 
                    message sent less than 10 minutes ago'   

我们使用message_interval方法变量来设置短信之间的时间间隔。我们不希望每次运动传感器检测到运动时都发送短信。在我们的情况下,短信之间的最短时间为600秒,或10分钟。

如果这是第一次,或者距离上次发送短信已经超过 10 分钟,那么代码将在测试环境或实际环境中发送短信。请注意15005550006电话号码在测试环境中的使用。实际环境需要您的 Twilio 号码,并且您自己的电话号码用于to字段。对于测试和实际环境,都会返回触发警报并发送短信的消息,然后是消息的 SID。不同之处在于您实际上不会收到短信(尽管代码中有调用 Twilio)。

如果距上次发送短信不到 10 分钟,则消息将显示触发警报并发送短信不到 10 分钟

在我们的主函数中,我们创建了一个SecurityDashboardDist对象,并将其命名为security_dashboard。通过不传入任何内容,我们允许默认情况下设置测试环境的仪表板:

if __name__=="__main__":  
    security_dashboard = SecurityDashboardDist()

    while True:
        sensory_data = SensoryData()
        sensory_dashboard = SensoryDashboard(sensory_data)
        print(security_dashboard.update_dashboard(
                sensory_dashboard))

        sleep(5)

随后的连续循环每 5 秒创建一个SensoryDataSensoryDashboard对象。SensoryData对象(sensory_data)用于实例化SensoryDashboard对象(sensory_dashboard),因为前者提供当前的感官数据,后者创建感官仪表板。

通过根据它们的名称命名我们的类,以及根据它们的功能命名我们的方法,代码变得相当自解释。

然后我们将这个SensoryDashboard对象(sensory_dashboard)传递给SecurityDashboard(security_dashboard)的update_dashboard方法。由于update_dashboard方法返回一个字符串,我们可以用它来打印到我们的 shell,从而看到我们的仪表板每 5 秒打印一次状态。我们将SecurityDashboardDist对象的实例化放在循环之外,因为我们只需要设置环境一次。

现在我们了解了代码,是时候在实际的 Twilio 环境中运行它了。请注意,当我们切换到实际环境时,代码中唯一改变的部分是实际发送短信。要将我们的仪表板变成一个实时发送短信的机器,只需将主方法的第一行更改为以下内容:

security_dashboard = SecurityDashboardDist(True)

摘要

完成本章后,我们应该非常熟悉将感应数据发布到物联网仪表板。我们还应该熟悉使用 Twilio 网络服务从树莓派发送短信。

我们将在第十一章中查看蓝牙库,使用蓝牙创建门铃按钮,然后将这些信息和我们在本章中获得的信息结合起来,制作一个物联网门铃。

问题

  1. 我们用来从树莓派发送短信的服务的名称是什么?

  2. 真或假?我们使用 PIR 传感器来读取温度和湿度值。

  3. 如何在 ThingsBoard 中创建仪表板?

  4. 真或假?我们通过使用感应仪表板来构建我们的增强安全仪表板。

  5. 我们用来读取温度和湿度感应数据的库的名称是什么?

  6. 真或假?我们需要预先安装用于发送短信的库与 Raspbian 一起。

  7. 在我们的代码中命名类时,我们试图做什么?

  8. 真或假?为了将我们的环境从测试切换到实际,我们是否需要重写增强家庭安全仪表板中的整个代码。

  9. 真或假?我们 Twilio 账户的account_sid号码在实际环境和测试环境中是相同的。

  10. 在我们的SecurityDashboardDist.py代码中,我们在哪里创建了SecurityDashboardDist对象?

进一步阅读

为了进一步了解 Twilio 和 ThingsBoard 背后的技术,请参考以下链接:

thingsboard.io/docs/

第十一章:使用蓝牙创建门铃按钮

在本章中,我们将把重点转向蓝牙。蓝牙是一种无线技术,用于在短距离内交换数据。它在 2.4 到 2.485 GHz 频段运行,通常的范围为 10 米。

在本章的项目中,我们将使用安卓上的蓝点应用程序,首先构建一个简单的蓝牙门铃,然后构建一个接受秘密滑动手势的更高级的门铃。

本章将涵盖以下主题:

  • 介绍蓝点

  • RGB LED 是什么?

  • 使用蓝牙和 Python 读取我们的按钮状态

项目概述

在本章中,我们将使用树莓派和安卓手机或平板电脑构建一个蓝牙门铃。我们将使用安卓手机或平板电脑上的一个名为蓝点的应用程序,该应用程序专为树莓派项目设计。

我们将从 RGB LED 开始,编写一个小程序来循环显示这三种颜色。然后,我们将使用 RGB LED 和有源蜂鸣器创建一个警报。我们将使用 Python 代码测试警报。

我们将编写 Python 代码来从蓝点读取按钮信息。然后,我们将结合警报和蓝点的代码来创建一个蓝牙门铃系统。

本章的项目应该需要一个上午或下午的时间来完成。

入门

完成此项目需要以下内容:

  • 树莓派 3 型号(2015 年或更新型号)

  • USB 电源适配器

  • 计算机显示器

  • USB 键盘

  • USB 鼠标

  • 面包板

  • 跳线线

  • 330 欧姆电阻器(3 个)

  • RGB LED

  • 有源蜂鸣器

  • 安卓手机或平板电脑

介绍蓝点

蓝点是一个安卓应用程序,可在 Google Play 商店中获得。它可以作为树莓派的蓝牙遥控器。加载到您的安卓手机或平板电脑后,它基本上是一个大蓝点,您按下它就会向树莓派发送信号。以下是一个加载到平板电脑上的蓝点应用程序的图片:

它可以作为一种蓝牙操纵杆,因为根据您如何与屏幕上的点交互,位置、滑块和旋转数据可以从应用程序发送到您的树莓派。我们将通过根据蓝点的按压方式创建自定义铃声,将一些功能添加到我们的门铃应用程序中。要在安卓手机或平板电脑上安装蓝点,请访问 Google Play 商店并搜索蓝点。

在树莓派上安装 bluedot 库

要在树莓派上安装bluedot库,请执行以下操作:

  1. 打开终端应用程序

  2. 在终端中输入以下内容:

sudo pip3 install bluedot
  1. Enter安装库

将蓝点与您的树莓派配对

为了使用蓝点应用程序,您必须将其与树莓派配对。要做到这一点,请按照以下步骤操作:

  1. 从树莓派桌面客户端的右上角,点击蓝牙符号:

  1. 如果蓝牙未打开,请点击蓝牙图标,然后选择打开蓝牙

  2. 从蓝牙下拉菜单中选择“使可发现”

  3. 在您的安卓手机或平板电脑上,转到蓝牙设置(这可能在手机或平板电脑上的特定操作系统上有不同的位置)

  4. 您应该能够在“可用设备”列表中看到树莓派

  5. 点击它以将您的设备与树莓派配对

  6. 您应该在树莓派上收到一条消息,内容类似于“设备'Galaxy Tab E'请求配对。您接受请求吗?”

  7. 点击“确定”接受

  8. 可能会收到“连接失败”消息。我能够忽略这条消息,仍然可以让蓝点应用程序与我的树莓派配对,所以不要太担心

  9. 将蓝点应用加载到您的安卓手机或平板电脑上

  10. 您应该看到一个列表,其中树莓派是其中的一项

  11. 点击树莓派项目以连接蓝点应用程序到树莓派

要测试我们的连接,请执行以下操作:

  1. 通过以下方式打开 Thonny:应用程序菜单 | 编程 | Thonny Python IDE

  2. 单击“新建”图标创建一个新文件

  3. 在文件中键入以下内容:

from bluedot import BlueDot
bd = BlueDot()
bd.wait_for_press()
print("Thank you for pressing the Blue Dot!")
  1. 将文件保存为bluest-test.py并运行它

  2. 您应该在 Thonny shell 中收到一条消息,上面写着服务器已启动,然后是树莓派的蓝牙地址

  3. 然后您会收到一条消息,上面写着等待连接

  4. 如果您的蓝点应用从树莓派断开连接,请通过在列表中选择树莓派项目来重新连接

  5. 一旦蓝点应用连接到树莓派,您将收到消息客户端已连接,然后是您手机或平板电脑的蓝牙地址

  6. 按下大蓝点

  7. Thonny shell 现在应该打印以下消息:感谢您按下蓝点!

接线我们的电路

我们将使用有源蜂鸣器和 RGB LED 创建一个门铃电路。由于我们之前没有讨论过 RGB LED,我们将快速看一下这个令人惊叹的小电子元件。然后,我们使用树莓派编写一个简单的测试程序,点亮 RGB LED 并发出有源蜂鸣器的声音。

什么是 RGB LED?

RGB LED 实际上只是一个单元内的三个 LED:一个红色,一个绿色,一个蓝色。通过在输入引脚的选择上以不同的功率电流来实现几乎可以达到任何颜色。以下是这样一个 LED 的图示:

您可以看到有红色、绿色和蓝色引脚,还有一个负极引脚(-)。当 RGB LED 有一个负极引脚(-)时,它被称为共阴极。一些 RGB LED 有一个共阳极引脚(+),因此被称为共阳极。对于我们的电路,我们将使用一个共阴极的 RGB LED。共阴极和共阳极都有 RGB LED 的最长引脚,并且通过这个特征来识别。

测试我们的 RGB LED

我们现在将建立一个电路,用它我们可以测试我们的 RGB LED。以下是我们电路的接线图:

要按照图中所示的电路搭建,请执行以下操作:

  1. 使用面包板,将 RGB LED 插入面包板,使得共阴极插入到左边第二个插槽中

  2. 将 330 欧姆电阻器连接到面包板中央间隙上的红色、绿色和蓝色引脚

  3. 从 GPIO 引脚 17 连接一根母对公跳线到面包板左侧的第一个插槽

  4. 从 GPIO GND 连接一根母对公跳线到 RGB LED 的阴极引脚(从左边数第二个)

  5. 从 GPIO 引脚 27 连接一根母对公跳线到面包板左侧的第三个插槽

  6. 从 GPIO 引脚 22 连接一根母对公跳线到面包板左侧的第四个插槽

  7. 从应用程序菜单 | 编程 | Thonny Python IDE 中打开 Thonny

  8. 单击“新建”图标创建一个新文件

  9. 在文件中键入以下内容:

from gpiozero import RGBLED
from time import sleep

led = RGBLED(red=17, green=27, blue=22)

while True:
   led.color=(1,0,0)
    sleep(2)
    led.color=(0,1,0)
    sleep(2)
    led.color=(0,0,1)
    sleep(2)
    led.off()
    sleep(2)    
  1. 将文件保存为RGB-LED-test.py并运行它

您应该看到 RGB LED 在红色亮起 2 秒钟。然后 RGB LED 应该在绿色亮起 2 秒钟,然后在蓝色亮起 2 秒钟。然后它将在 2 秒钟内关闭,然后再次开始序列。

在代码中,我们首先从 GPIO Zero 库导入RGBLED。然后,我们通过为 RGB LED 的红色、绿色和蓝色分配引脚号来设置一个名为led的变量。从那里,我们只需使用led.color属性打开每种颜色。很容易看出,将值1, 0, 0分配给led.color属性会打开红色 LED 并关闭绿色和蓝色 LED。led.off方法关闭 RGB LED。

尝试尝试不同的led.color值。您甚至可以输入小于1的值来改变颜色的强度(范围是01之间的任何值)。如果您仔细观察,您可能能够看到 RGB LED 内部不同的 LED 灯亮起。

完成我们的门铃电路

现在让我们向我们的电路中添加一个有源蜂鸣器,以完成我们门铃系统的构建。以下是我们门铃电路的图表:

要构建电路,请按照以下步骤进行:

  1. 使用我们现有的电路,在面包板的另一端插入一个有源蜂鸣器

  2. 将母对公跳线从 GPIO 引脚 26 连接到有源蜂鸣器的正引脚

  3. 将母对公跳线从 GPIO GND 连接到有源蜂鸣器的负引脚

  4. 从应用程序菜单中打开 Thonny |编程| Thonny Python IDE

  5. 单击新图标创建新文件

  6. 在文件中键入以下内容:

from gpiozero import RGBLED
from gpiozero import Buzzer
from time import sleep

class DoorbellAlarm:

    led = RGBLED(red=17, green=22, blue=27)
    buzzer = Buzzer(26)
    num_of_times = 0

    def __init__(self, num_of_times):
        self.num_of_times = num_of_times

    def play_sequence(self):
        num = 0
        while num < self.num_of_times:
            self.buzzer.on()
            self.light_show()
            sleep(0.5)
            self.buzzer.off()
            sleep(0.5)
            num += 1

    def light_show(self):
        self.led.color=(1,0,0)
        sleep(0.1)
        self.led.color=(0,1,0)
        sleep(0.1)
        self.led.color=(0,0,1)
        sleep(0.1)
        self.led.off()

if __name__=="__main__":

    doorbell_alarm = DoorbellAlarm(5)
    doorbell_alarm.play_sequence()   
  1. 将文件保存为DoorbellAlarm.py并运行它

  2. 您应该听到蜂鸣器响了五次,并且 RGB LED 也应该按相同的次数进行灯光序列

让我们来看看代码:

  1. 我们首先通过导入所需的库来开始:
from gpiozero import RGBLED
from gpiozero import Buzzer
from time import sleep
  1. 之后,我们使用DoorbellAlarm类名创建我们的类,然后设置初始值:
led = RGBLED(red=17, green=22, blue=27)
buzzer = Buzzer(26)
num_of_times = 0
  1. 类初始化使用num_of_times类变量设置警报序列将播放的次数:
def __init__(self, num_of_times):
    self.num_of_times = num_of_times
  1. light_show方法只是按顺序闪烁 RGB LED 中的每种颜色,持续0.1秒:
def light_show(self):
    self.led.color=(1,0,0)
    sleep(0.1)
    self.led.color=(0,1,0)
    sleep(0.1)
    self.led.color=(0,0,1)
    sleep(0.1)
    self.led.off()
  1. play_sequence方法打开和关闭蜂鸣器的次数设置在初始化DoorbellAlarm类时。每次蜂鸣器响起时,它还会运行 RGB LED light_show函数:
def play_sequence(self):
    num = 0
    while num < self.num_of_times:
        self.buzzer.on()
        self.light_show()
        sleep(0.5)
        self.buzzer.off()
        sleep(0.5)
        num += 1
  1. 我们通过用值5实例化DoorbellAlarm类并将其分配给doorbell_alarm变量来测试我们的代码。然后通过调用play_sequence方法来播放序列:
if __name__=="__main__":

    doorbell_alarm = DoorbellAlarm(5)
    doorbell_alarm.play_sequence()   

使用蓝牙和 Python 读取我们的按钮状态

如前所述,我们能够以更多方式与 Blue Dot 应用进行交互,而不仅仅是简单的按钮按下。Blue Dot 应用可以解释用户在按钮上按下的位置,以及检测双击和滑动。在以下代码中,我们将使用 Python 从 Blue Dot 应用中读取。

使用 Python 读取按钮信息

做以下事情:

  1. 从应用程序菜单中打开 Thonny |编程| Thonny Python IDE

  2. 单击新图标创建新文件

  3. 在文件中键入以下内容:

from bluedot import BlueDot
from signal import pause

class BlueDotButton:

    def swiped(swipe):

        if swipe.up:
            print("Blue Dot Swiped Up")
        elif swipe.down:
            print("Blue Dot Swiped Down")
        elif swipe.left:
            print("Blue Dot Swiped Left")
        elif swipe.right:
            print("Blue Dot Swiped Right")

    def pressed(pos):
        if pos.top:
            print("Blue Dot Pressed from Top")
        elif pos.bottom:
            print("Blue Dot Pressed from Bottom")
        elif pos.left:
            print("Blue Dot Pressed from Left")
        elif pos.right:
            print("Blue Dot Pressed from Right")
        elif pos.middle:
            print("Blue Dot Pressed from Middle")

    def double_pressed():
        print("Blue Dot Double Pressed")

    blue_dot = BlueDot()
    blue_dot.when_swiped = swiped
    blue_dot.when_pressed = pressed
    blue_dot.when_double_pressed = double_pressed

 if __name__=="__main__":

    blue_dot_button = BlueDotButton()
    pause()       
  1. 将文件保存为BlueDotButton.py并运行它

每次运行此程序时,您可能需要将 Blue Dot 应用连接到您的 Raspberry Pi(只需从 Blue Dot 应用中的列表中选择它)。尝试在中间,顶部,左侧等处按下 Blue Dot。您应该在 shell 中看到告诉您按下的位置的消息。现在尝试滑动和双击。shell 中的消息也应指示这些手势。

那么,我们在这里做了什么?让我们来看看代码:

  1. 我们首先通过导入所需的库来开始:
from bluedot import BlueDot
from signal import pause

我们显然需要BlueDot,我们还需要pause。我们使用pause来暂停程序,并等待来自 Blue Dot 应用的信号。由于我们正在使用when_pressedwhen_swipedwhen_double_swiped事件,我们需要暂停和等待(而不是其他方法,如wait_for_press)。我相信使用when而不是wait类型的事件使代码更清晰。

  1. 在我们的程序的核心是实例化BlueDot对象及其相关的回调定义:
blue_dot = BlueDot()
blue_dot.when_swiped = swiped
blue_dot.when_pressed = pressed
blue_dot.when_double_pressed = double_pressed

请注意,这些回调定义必须放在它们所引用的方法之后,否则将会出错。

  1. 方法本身非常简单。以下是swiped方法:
def swiped(swipe):

    if swipe.up:
        print("Blue Dot Swiped Up")
    elif swipe.down:
        print("Blue Dot Swiped Down")
    elif swipe.left:
        print("Blue Dot Swiped Left")
    elif swipe.right:
        print("Blue Dot Swiped Right")
  1. 我们使用方法定义了一个名为swipe的变量。请注意,在方法签名中我们不必使用self,因为我们在方法中没有使用类变量。

创建蓝牙门铃

现在我们知道如何从 Blue Dot 读取按钮信息,我们可以构建一个蓝牙门铃按钮。我们将重写我们的DoorbellAlarm类,并使用来自 Blue Dot 的简单按钮按下来激活警报,如下所示:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 单击新图标创建新文件

  3. 在文件中键入以下内容:

from gpiozero import RGBLED
from gpiozero import Buzzer
from time import sleep

class DoorbellAlarmAdvanced:

    led = RGBLED(red=17, green=22, blue=27)
    buzzer = Buzzer(26)
    num_of_times = 0
    delay = 0

    def __init__(self, num_of_times, delay):
        self.num_of_times = num_of_times
        self.delay = delay

    def play_sequence(self):
        num = 0
        while num < self.num_of_times:
            self.buzzer.on()
            self.light_show()
            sleep(self.delay)
            self.buzzer.off()
            sleep(self.delay)
            num += 1

    def light_show(self):
        self.led.color=(1,0,0)
        sleep(0.1)
        self.led.color=(0,1,0)
        sleep(0.1)
        self.led.color=(0,0,1)
        sleep(0.1)
        self.led.off()

if __name__=="__main__":

    doorbell_alarm = DoorbellAlarmAdvanced(5,1)
    doorbell_alarm.play_sequence()
  1. 将文件保存为DoorbellAlarmAdvanced.py

我们的新类DoorbellAlarmAdvancedDoorbellAlarm类的修改版本。我们所做的基本上是添加了一个我们称之为delay的新类属性。这个类属性将用于改变蜂鸣器响铃之间的延迟时间。正如您在代码中看到的,为了进行这一更改而修改的两个方法是__init__play_sequence

现在我们已经对我们的警报进行了更改,让我们创建一个简单的门铃程序如下:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 单击新图标创建新文件

  3. 在文件中键入以下内容:

from bluedot import BlueDot
from signal import pause
from DoorbellAlarmAdvanced import DoorbellAlarmAdvanced

class SimpleDoorbell:

 def pressed():
 doorbell_alarm = DoorbellAlarmAdvanced(5, 1)
 doorbell_alarm.play_sequence()

 blue_dot = BlueDot()
 blue_dot.when_pressed = pressed

if __name__=="__main__":

 doorbell_alarm = SimpleDoorbell()
 pause()
  1. 将文件保存为SimpleDoorbell.py并运行

  2. 将蓝点应用程序连接到树莓派,如果尚未连接

  3. 按下大蓝点

您应该听到五声持续一秒钟的响铃,每隔一秒钟响一次。您还会看到 RGB LED 经历了一个短暂的灯光秀。正如您所看到的,代码非常简单。我们导入我们的新DoorbellAlarmAdvanced类,然后在pressed方法中使用doorbell_alarm变量初始化类后调用play_sequence方法。

我们在创建DoorbellAlarmAdvanced类时所做的更改被用于我们的代码,以允许我们设置响铃之间的延迟时间。

创建一个秘密蓝牙门铃

在我们回答门铃之前知道谁在门口会不会很好?我们可以利用蓝点应用程序的滑动功能。要创建一个秘密的蓝牙门铃(秘密是我们与门铃互动的方式,而不是门铃的秘密位置),请执行以下操作:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 单击新图标创建新文件

  3. 在文件中键入以下内容:

from bluedot import BlueDot
from signal import pause
from DoorbellAlarmAdvanced import DoorbellAlarmAdvanced

class SecretDoorbell:

    def swiped(swipe):

        if swipe.up:
            doorbell_alarm = DoorbellAlarmAdvanced(5, 0.5)
            doorbell_alarm.play_sequence()
        elif swipe.down:
            doorbell_alarm = DoorbellAlarmAdvanced(3, 2)
            doorbell_alarm.play_sequence()
        elif swipe.left:
            doorbell_alarm = DoorbellAlarmAdvanced(1, 5)
            doorbell_alarm.play_sequence()
        elif swipe.right:
            doorbell_alarm = DoorbellAlarmAdvanced(1, 0.5)
            doorbell_alarm.play_sequence()

    blue_dot = BlueDot()
    blue_dot.when_swiped = swiped    

if __name__=="__main__":

    doorbell = SecretDoorbell()
    pause()
  1. 将文件保存为SecretDoorbell.py并运行

  2. 将蓝点应用程序连接到树莓派,如果尚未连接

  3. 向上滑动蓝点

您应该听到五声短促的响铃,同时看到 RGB LED 的灯光秀。尝试向下、向左和向右滑动。每次您应该得到不同的响铃序列。

那么,我们在这里做了什么?基本上,我们将回调附加到when_swiped事件,并通过if语句,创建了具有不同初始值的新DoorbellAlarmAdvanced对象。

通过这个项目,我们现在可以知道谁在门口,因为我们可以为不同的朋友分配各种滑动手势。

摘要

在本章中,我们使用树莓派和蓝点安卓应用程序创建了一个蓝牙门铃应用程序。我们首先学习了一些关于 RGB LED 的知识,然后将其与主动蜂鸣器一起用于警报电路。

通过蓝点应用程序,我们学会了如何将蓝牙按钮连接到树莓派。我们还学会了如何使用一些蓝点手势,并创建了一个具有不同响铃持续时间的门铃应用程序。

在第十二章中,增强我们的物联网门铃,我们将扩展我们的门铃功能,并在有人按下按钮时发送文本消息。

问题

  1. RGB LED 与普通 LED 有什么不同?

  2. 正确还是错误?蓝点应用程序可以在 Google Play 商店中找到。

  3. 什么是共阳极?

  4. 正确还是错误?RGB LED 内的三种颜色是红色、绿色和黄色。

  5. 如何将蓝点应用程序与树莓派配对?

  6. 正确还是错误?蓝牙是一种用于极长距离的通信技术。

  7. DoorbellAlarmDoorbellAlarmAdvanced之间有什么区别?

  8. 正确还是错误?GPIO Zero 库包含一个名为RGBLED的类。

  9. 正确还是错误?蓝点应用程序可以用于记录滑动手势。

  10. SimpleDoorbellSecretDoorbell类之间有什么区别?

进一步阅读

要了解更多关于 Blue Dot Android 应用程序的信息,请访问文档页面bluedot.readthedocs.io

第十二章:增强我们的物联网门铃

在第十章中,我们探索了网络服务。然后在第十一章中引入了蓝牙,并使用 Android 应用蓝点和我们的树莓派构建了蓝牙门铃。

在本章中,我们将通过添加在有人敲门时发送短信的功能来增强我们的蓝牙门铃。我们将运用所学知识,并使用我们在第十章中设置的 Twilio 账户,添加短信功能。

本章将涵盖以下主题:

  • 有人敲门时发送短信

  • 创建一个带有短信功能的秘密门铃应用

项目概述

在本章的两个项目中,我们将使用第十一章中的电路,同时还将使用 Android 设备上的蓝点应用,如第十一章中所述。以下是本章中我们将创建的应用的图表:

我们将创建这个应用的两个版本。我们的应用的第一个版本将是一个简单的蓝牙门铃,按下蓝点会触发蜂鸣器和 RGB LED 灯光秀。警报触发后,将使用 Twilio 云服务发送一条短信。

应用程序的修改版本将使用蓝点应用上的滑动手势来指示特定的访客。四位潜在的访客将各自拥有自己独特的蓝点滑动手势。在自定义蜂鸣器响铃和 RGB LED 灯光秀之后,将发送一条文本消息通知收件人门口有谁。Twilio 云也将用于此功能。

这两个项目应该需要一个上午或一个下午的时间来完成。

入门

完成此项目需要以下步骤:

  • 树莓派 3 型(2015 年或更新型号)

  • USB 电源适配器

  • 计算机显示器

  • USB 键盘

  • USB 鼠标

  • 面包板

  • 跳线线

  • 330 欧姆电阻(3 个)

  • RGB LED

  • 有源蜂鸣器

  • Android 设备(手机/平板)

有人敲门时发送短信

在第十章中,我们使用了一种叫做 Twilio 的技术来创建文本消息。在那个例子中,我们使用 Twilio 在检测到入侵者时发送文本消息。在第十一章中,我们使用了 Android 手机或平板上的蓝点应用创建了一个蓝牙门铃。门铃响起蜂鸣器,并在 RGB LED 上进行了一些灯光秀。

对于这个项目,我们将结合 Twilio 和蓝牙门铃,当有人按下蓝点门铃时,将发送一条短信(参考第十章和第十一章,熟悉这些技术)。

创建一个带有短信功能的简单门铃应用

要创建我们的简单门铃应用,请执行以下操作:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 中打开 Thonny

  2. 点击“新建”图标创建一个新文件

  3. 输入以下内容:

from twilio.rest import Client
from gpiozero import RGBLED
from gpiozero import Buzzer
from bluedot import BlueDot
from signal import pause
from time import sleep

class Doorbell:
    account_sid = ''
    auth_token = ''
    from_phonenumber=''
    test_env = True
    led = RGBLED(red=17, green=22, blue=27)
    buzzer = Buzzer(26)
    num_of_rings = 0
    ring_delay = 0
    msg = ''

    def __init__(self, 
                 num_of_rings = 1, 
                 ring_delay = 1, 
                 message = 'ring', 
                 test_env = True):
        self.num_of_rings = num_of_rings
        self.ring_delay = ring_delay
        self.message = message
        self.test_env = self.setEnvironment(test_env)

    def setEnvironment(self, test_env):
        if test_env:
            self.account_sid = '<<test account_sid>>'
            self.auth_token = '<<test auth_token>>'
            return True
        else:
            self.account_sid = '<<live account_sid>>'
            self.auth_token = '<<live auth_token>>'
            return False

    def doorbell_sequence(self):
        num = 0
        while num < self.num_of_rings:
            self.buzzer.on()
            self.light_show()
            sleep(self.ring_delay)
            self.buzzer.off()
            sleep(self.ring_delay)
            num += 1
        return self.sendTextMessage()

    def sendTextMessage(self):
        twilio_client = Client(self.account_sid, self.auth_token)
        if self.test_env:
            message = twilio_client.messages.create(
                        body=self.message,
                        from_= '+15005550006',
                        to='<<your phone number>>'
            )
        else:
            message = twilio_client.messages.create(
                        body=self.message,
                        from_= '<<your twilio number>>',
                        to='<<your phone number>>'
            ) 
        return 'Doorbell text message sent - ' + message.sid

    def light_show(self):
        self.led.color=(1,0,0)
        sleep(0.5)
        self.led.color=(0,1,0)
        sleep(0.5)
        self.led.color=(0,0,1)
        sleep(0.5)
        self.led.off()

def pressed():
    doorbell = Doorbell(2, 0.5, 'There is someone at the door')
    print(doorbell.doorbell_sequence())

blue_dot = BlueDot()
blue_dot.when_pressed = pressed

if __name__=="__main__":
    pause()

  1. 将文件保存为Doorbell.py并运行

  2. 在您的 Android 设备上打开蓝点应用

  3. 连接到树莓派

  4. 按下大蓝点

你应该听到铃声并看到灯光序列循环两次,两次之间有短暂的延迟。你应该在 shell 中得到类似以下的输出:

Server started B8:27:EB:12:77:4F
Waiting for connection
Client connected F4:0E:22:EB:31:CA
Doorbell text message sent - SM5cf1125acad44016840a6b76f99b3624

前三行表示 Blue Dot 应用程序已通过我们的 Python 程序连接到我们的 Raspberry Pi。最后一行表示已发送了一条短信。由于我们使用的是测试环境,实际上没有发送短信,但是调用了 Twilio 服务。

让我们来看看代码。我们首先定义了我们的类,并给它命名为Doorbell。这是我们类的一个很好的名字,因为我们已经编写了我们的代码,使得一切与门铃有关的东西都包含在Doorbell.py文件中。这个文件包含了Doorbell类,用于提醒用户,以及 Blue Dot 代码,用于触发门铃。Blue Dot 代码实际上位于Doorbell类定义之外,因为我们认为它是 Blue Dot 应用的一部分,而不是门铃本身。我们当然可以设计我们的代码,使得Doorbell类包含触发警报的代码;然而,将警报与警报触发器分开使得在将来更容易重用Doorbell类作为警报机制。

选择类名可能有些棘手。然而,选择正确的类名非常重要,因为使用适合其预期用途的类名将更容易构建应用程序。类名通常是名词,类中的方法是动词。通常,最好让一个类代表一件事或一个想法。例如,我们将我们的类命名为Doorbell,因为我们已经设计它来封装门铃的功能:提醒用户有人在门口。考虑到这个想法,Doorbell类包含点亮 LED、发出蜂鸣器声音和发送短信的代码是有意义的,因为这三个动作都属于提醒用户的想法。

在我们定义了我们的类之后,我们创建了以下用于我们类的类变量:

class Doorbell:
    account_sid = ''
    auth_token = ''
    from_phonenumber=''
    test_env = True
    led = RGBLED(red=17, green=22, blue=27)
    buzzer = Buzzer(26)
    num_of_rings = 0
    ring_delay = 0
    msg = ''

initsetEnvironment方法设置了我们在类中使用的变量。test_env变量确定我们在代码中使用 Twilio 测试环境还是实时环境。测试环境是默认使用的:

def __init__(self, 
             num_of_rings = 1, 
             ring_delay = 1, 
             message = 'ring', 
             test_env = True):
     self.num_of_rings = num_of_rings
     self.ring_delay = ring_delay
     self.message = message
     self.test_env = self.setEnvironment(test_env)

 def setEnvironment(self, test_env):
     if test_env:
         self.account_sid = '<<test account sid>>'
         self.auth_token = '<<test auth token>>'
         return True
     else:
         self.account_sid = '<<live account sid>>'
         self.auth_token = '<<auth_token>>'
         return False

doorbell_sequencesendTextMessagelight_show方法与本书先前介绍的方法类似。通过这三种方法,我们通知用户有人在门口。这里需要注意的是从sendTextMessage方法发送的返回值:return 'Doorbell text message sent - ' + message.sid。通过在代码中加入这一行,我们能够使用sendTextMessage方法在我们的 shell 中提供一个打印确认,即已发送了一条短信。

如前所述,我们的代码中的 Blue Dot 部分位于类定义之外:

def pressed():
    doorbell = Doorbell(2, 0.5, 'There is someone at the door')
    print(doorbell.doorbell_sequence())

blue_dot = BlueDot()
blue_dot.when_pressed = pressed

前面的代码是我们以前见过的。我们定义了pressed方法,在这里我们实例化了一个新的doorbell对象,然后调用了doorbelldoorbell_sequence方法。blue_dot变量是一个BlueDot对象,我们只关心when_pressed事件。

这里需要注意的是包含doorbell = Doorbell(2, 0.5, 'There is someone at the door')语句的那一行。在这一行中,我们实例化了一个Doorbell对象,我们称之为doorbellnum_of_rings等于2ring_delay(或持续时间)等于0.5;消息等于门口有人。我们没有传递test_env环境值。因此,默认设置为True,用于设置我们的doorbell对象使用 Twilio 测试环境,不发送短信。要更改为发送短信,将语句更改为:

doorbell = Doorbell(2, 0.5, 'There is someone at the door', False)

确保您相应地设置了 Twilio 帐户参数。您应该收到一条短信,告诉您有人在门口。以下是我在 iPhone 上收到的消息:

创建一个带有短信功能的秘密门铃应用程序

现在我们有能力在安卓设备上的大蓝色按钮被按下时发送文本消息,让我们把它变得更加复杂一些。我们将修改我们在第十一章中创建的SecretDoorbell类,使用蓝牙创建门铃按钮,并赋予它发送文本消息告诉我们谁在门口的能力。就像之前一样,我们将把所有的代码放在一个文件中以保持紧凑:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 点击新建图标创建一个新文件

  3. 输入以下内容:

from twilio.rest import Client
from gpiozero import RGBLED
from gpiozero import Buzzer
from bluedot import BlueDot
from signal import pause
from time import sleep

class Doorbell:
    account_sid = ''
    auth_token = ''
    from_phonenumber=''
    test_env = True
    led = RGBLED(red=17, green=22, blue=27)
    buzzer = Buzzer(26)
    num_of_rings = 0
    ring_delay = 0
    msg = ''

    def __init__(self, 
                 num_of_rings = 1, 
                 ring_delay = 1, 
                 message = 'ring', 
                 test_env = True):
        self.num_of_rings = num_of_rings
        self.ring_delay = ring_delay
        self.message = message
        self.test_env = self.setEnvironment(test_env)

    def setEnvironment(self, test_env):
        if test_env:
            self.account_sid = '<<test account_sid>>'
            self.auth_token = '<<test auth_token>>'
            return True
        else:
            self.account_sid = '<<live account_sid>>'
            self.auth_token = '<<live auth_token>>'
            return False

    def doorbell_sequence(self):
        num = 0
        while num < self.num_of_rings:
            self.buzzer.on()
            self.light_show()
            sleep(self.ring_delay)
            self.buzzer.off()
            sleep(self.ring_delay)
            num += 1
        return self.sendTextMessage()

    def sendTextMessage(self):
        twilio_client = Client(self.account_sid, self.auth_token)
        if self.test_env:
            message = twilio_client.messages.create(
                        body=self.message,
                        from_= '+15005550006',
                        to='<<your phone number>>'
            )
        else:
            message = twilio_client.messages.create(
                        body=self.message,
                        from_= '<<your twilio number>>',
                        to='<<your phone number>>'
            ) 
        return 'Doorbell text message sent - ' + message.sid

    def light_show(self):
        self.led.color=(1,0,0)
        sleep(0.5)
        self.led.color=(0,1,0)
        sleep(0.5)
        self.led.color=(0,0,1)
        sleep(0.5)
        self.led.off()

class SecretDoorbell(Doorbell):
    names=[['Bob', 4, 0.5], 
           ['Josephine', 1, 3], 
           ['Ares', 6, 0.2], 
           ['Constance', 2, 1]]
    message = ' is at the door!'

    def __init__(self, person_num, test_env = True):
        Doorbell.__init__(self,
                          self.names[person_num][1],
                          self.names[person_num][2],
                          self.names[person_num][0] + self.message,
                          test_env)

def swiped(swipe):
    if swipe.up:
        doorbell = SecretDoorbell(0)
        print(doorbell.doorbell_sequence())
    elif swipe.down:
        doorbell = SecretDoorbell(1)
        print(doorbell.doorbell_sequence())
    elif swipe.left:
        doorbell = SecretDoorbell(2)
        print(doorbell.doorbell_sequence())
    elif swipe.right:
        doorbell = SecretDoorbell(3)
        print(doorbell.doorbell_sequence())

blue_dot = BlueDot()
blue_dot.when_swiped = swiped

if __name__=="__main__":
    pause()
  1. 将文件保存为SecretDoorbell.py并运行它

  2. 在您的安卓设备上打开蓝点应用

  3. 连接到树莓派

  4. 从顶部位置向下滑动蓝点

  5. 您应该听到蜂鸣器响一次,大约持续三秒钟,并且看到 RGB LED 进行一次灯光表演。在 shell 底部将显示类似以下内容:

Server started B8:27:EB:12:77:4F
Waiting for connection
Client connected F4:0E:22:EB:31:CA
Doorbell text message sent - SM62680586b32a42bdacaff4200e0fed78
  1. 和之前的项目一样,我们将会收到一条文本消息已发送的消息,但实际上我们不会收到文本消息,因为我们处于 Twilio 测试环境中

在让我们的应用程序根据他们的滑动给我们发送一条告诉我们门口有谁的短信之前,让我们看一下代码。

我们的SecretDoorbell.py文件与我们的Doorbell.py文件完全相同,除了以下代码:

class SecretDoorbell(Doorbell):
    names=[['Bob', 4, 0.5], 
           ['Josephine', 1, 3], 
           ['Ares', 6, 0.2], 
           ['Constance', 2, 1]]
    message = ' is at the door!'

    def __init__(self, person_num, test_env = True):
        Doorbell.__init__(self,
                          self.names[person_num][1],
                          self.names[person_num][2],
                          self.names[person_num][0] + self.message,
                          test_env)

def swiped(swipe):
    if swipe.up:
        doorbell = SecretDoorbell(0)
        print(doorbell.doorbell_sequence())
    elif swipe.down:
        doorbell = SecretDoorbell(1)
        print(doorbell.doorbell_sequence())
    elif swipe.left:
        doorbell = SecretDoorbell(2)
        print(doorbell.doorbell_sequence())
    elif swipe.right:
        doorbell = SecretDoorbell(3)
        print(doorbell.doorbell_sequence())

blue_dot = BlueDot()
blue_dot.when_swiped = swiped

SecretDoorbell类被创建为Doorbell的子类,从而继承了Doorbell的方法。我们创建的names数组存储了数组中的名称和与名称相关的铃声属性。例如,第一个元素的名称是Bobnum_of_rings值为4ring_delay(持续时间)值为0.5。当这条记录在 Twilio 实时环境中使用时,您应该听到蜂鸣器响四次,并看到 RGB LED 灯光表演循环,之间有短暂的延迟。SecretDoorbellinit方法收集person_num(或者基本上是names数组中的位置信息),并用它来实例化Doorbell父类。test_env值默认为True,这意味着我们只能通过明确覆盖这个值来打开 Twilio 实时环境。这样可以防止我们在准备好部署应用程序之前意外使用完 Twilio 账户余额。

我们文件中的蓝点代码位于SecretDoorbell类定义之外。和之前的项目一样,这样做可以让我们将门铃功能与门铃触发器(我们安卓设备上的蓝点应用)分开。

在我们的蓝点代码中,我们实例化一个名为blue_dotBlueDot对象,然后将when_swiped事件赋给swiped。在swiped中,我们实例化一个SecretDoorbell对象,为swipe.up手势赋值0,为swipe.down赋值1,为swipe.left赋值2,为swipe.right赋值3。这些值对应于SecretDoorbell类的names数组中的位置。我们在为任何手势实例化SecretDoorbell对象时不传递test_env的值,因此不会发送文本消息。就像之前的项目一样,我们在 shell 中打印doorbell_sequence方法运行成功的结果。

要发送文本消息,我们只需要用False值覆盖默认的test_env值。我们在swiped方法中为我们的滑动手势实例化SecretDoorbell对象时这样做。我们的代码设计成这样的方式,我们可以为一个或多个手势发送文本消息。修改swiped中的以下elif语句:

elif swipe.down:
    doorbell = SecretDoorbell(1, False)
    print(doorbell.doorbell_sequence())

我们在这里所做的是通过覆盖test_env变量,为swipe.down手势打开了 Twilio 实时环境。我们为SecretDoorbell对象实例化时使用的1值对应于SecretDoorbellnames数组中的第二个元素。

因此,当你运行应用程序并在蓝点上从上向下滑动时,你应该收到来自 Twilio 的一条短信,内容是 Josephine 在门口,如下所示:

摘要

在本章中,我们学习了如何将短信功能添加到我们的门铃应用程序中。这使得门铃适应了物联网时代。很容易看出物联网蓝牙门铃的概念可以被扩展——想象一下当有人按门铃时打开门廊灯。

我们还可以看到蓝点应用程序也可以以其他方式被利用。我们可以使用蓝点应用程序编程特定的滑动序列,也许是为了解锁门。想象一下不必随身携带钥匙!

这是我们介绍我们的机器人车之前的最后一章。在接下来的章节中,我们将把我们迄今为止学到的概念应用到我们通过互联网控制的机器人上。

问题

  1. 蓝点应用程序如何连接到我们的树莓派?

  2. 正确还是错误?通过 Twilio 测试环境运行消息会创建一条发送到你手机的短信。

  3. 我们用来发送短信的服务的名称是什么?

  4. 正确还是错误?我们将我们的SecretDoorbell类创建为Doorbell类的子类。

  5. 我们在第二个应用程序中使用的四个蓝点手势是什么?

  6. 正确还是错误?以描述其功能的方式命名一个类会使编码变得更容易。

  7. DoorbellSecretDoorbell之间有什么区别?

  8. 正确还是错误?Josephine 的铃声模式包括一个长的蜂鸣声。

  9. 正确还是错误?为了从我们的应用程序接收短信,你需要使用安卓手机。

  10. 康斯坦斯应该如何滑动蓝点,这样我们就知道是她在门口?

进一步阅读

我们稍微涉及了 Twilio 服务。然而,还有更多需要学习的地方——访问www.twilio.com/docs/tutorials获取更多信息。

第十三章:介绍树莓派机器人车

我想向您介绍 T.A.R.A.S,这辆机器人车。T.A.R.A.S 实际上是一个回文缩略词;我从一个帮助我起步的商业导师那里得到了这个名字。在绞尽脑汁之后,我终于想出了如何将我的朋友 Taras 变成 T.A.R.A.S,这个令人惊叹的树莓派自动安全代理。从名字上您可能能够猜到,T.A.R.A.S 将为我们监视事物并充当自动安全警卫。

T.A.R.A.S 将使用树莓派作为大脑和电机驱动板,以控制摄像机云台和车轮的运动。T.A.R.A.S 还将具有感应输入以及 LED 和蜂鸣器输出。T.A.R.A.S 将是我们在本书中所学技能的集合。

我们将在本章中组装 T.A.R.A.S 并编写控制代码。

本章将涵盖以下主题:

  • 机器人车的零件

  • 构建机器人车

机器人车的零件

我设计 T.A.R.A.S 尽可能简单地组装。T.A.R.A.S 由激光切割的硬纸板底盘、3D 打印的车轮和摄像头支架部件组成(也有使用激光切割车轮支架的选项)。为了您能够组装 T.A.R.A.S,我提供了底盘的 SVG 文件和 3D 打印部件的 STL 文件。所有其他零件可以在线购买。以下是 T.A.R.A.S 组装前的照片:

  1. 伺服摄像头支架(已组装)—在www.aliexpress.com搜索防抖动摄像头支架

  2. 3D 打印支架(摄像头支架)

  3. 车轮用直流电机(带有电机线和延长线)—在www.aliexpress.com搜索智能车机器人塑料轮胎轮

  4. 车轮支架(3D 打印)

  5. LED 灯

  6. LED 灯座—在www.aliexpress.com搜索灯 LED 灯座黑色夹子

  7. 摄像头支架支撑(激光切割)

  8. 有源蜂鸣器—在www.aliexpress.com搜索 5V 有源蜂鸣器

  9. 距离传感器(HC-SR04)—在www.aliexpress.com搜索 HC-SR04

  10. 替代车轮支架(激光切割)

  11. 树莓派摄像头(长焦镜头版本,不带电缆)—在www.aliexpress.com搜索

  12. 电机驱动板(激光切割)

  13. 车轮—在www.aliexpress.com搜索智能车机器人塑料轮胎轮

  14. 机器人车底盘(激光切割)

  15. 电机驱动板—在www.aliexpress.com搜索 L298N 电机驱动板模块

  16. DC 插孔(带有连接的电线)—www.aliexpress.com

  17. Adafruit 16 通道 PWM/舵机 HAT—www.adafruit.com/product/2327

  18. 树莓派

  19. 40 针单排公针排连接器条(未显示)—www.aliexpress.com

  20. 各种松散的电线和面包板跳线(未显示)—购买许多不同的电线和面包板跳线是个好主意;您可以在www.aliexpress.com搜索面包板跳线

  21. 热缩管(未显示)

  22. 带 DC 插头的 7.4V 可充电电池(未显示)—在www.aliexpress.com搜索 7.4V 18650 锂离子可充电电池组(确保选择与 16 号 DC 插孔匹配的电池)

  23. 与零件 22 相比,您可以使用 AA 号电池存储盒来替代零件 16 和 22—www.aliexpress.com

  24. 迷你面包板(未显示)—在www.aliexpress.com搜索 SYB-170 迷你无焊点原型实验测试面包板

  25. 各种支架(未显示)-它应该能够具有至少 40 毫米的支架高度;最好尽可能多地使用支架,因为它们似乎总是派上用场,您可以在www.aliexpress.com上搜索电子支架

  26. 330 和 470 欧姆电阻器(未显示)-购买许多电阻器是个好主意;在www.aliexpress.com上搜索电阻器包

  27. 便携式 USB 电源包(未显示)-这种类型用于在外出时给手机充电;我们将使用此电源包为树莓派供电

组装机器人汽车

以下是构建 T.A.R.A.S,我们的机器人汽车的步骤。您的 T.A.R.A.S 版本可能与本书中使用的版本相似,也可以根据需要进行修改。首先,我使用了带有较长镜头的树莓派相机模块(夜视模型带有较长的镜头)。我还使用 Adafruit 16 通道 PWM /伺服 HAT 来驱动相机支架的伺服。您可以选择使用另一个板,或者放弃伺服,将相机安装在固定位置。

我最喜欢的机器人缩写词之一是来自 1980 年迪士尼电影《黑洞》的 Vincent。 Vincent,或更准确地说,V.I.N.CENT,代表着必要的集中信息。如果您知道这部电影,您将知道 V.I.N.CENT 非常聪明和非常有礼貌。 V.I.N.CENT 也有点自以为是,有时可能有点烦人。

我提供了两种不同的安装轮毂电机的方法:使用 3D 打印的轮毂支架或使用激光切割的轮毂支架。我更喜欢 3D 打印的支架,因为它可以使螺丝嵌入,从而在底盘和轮毂之间提供更多空间。

如果您自己 3D 打印轮毂支架和相机支架,可以使用任何您喜欢的固体丝材类型。就我个人而言,我使用 PETG,因为我喜欢它的弯曲性而不易断裂。 PLA 也可以。请确保将轮毂支架侧向打印,以便它们打印宽而不是高。这将导致打印可能在孔周围有点凌乱(至少对于 PETG 来说),但它将是一个更坚固的零件。我设法在 30 分钟内打印了一个轮毂支架,相机支架大约 90 分钟。

组装机器人汽车应该花费您一下午的时间。

第 1 步 - 用于树莓派的 Adafruit 16 通道 PWM /伺服 HAT

如果您还没有听说过,纽约市有一家名为 Adafruit 的令人惊叹的公司,为全球的电子爱好者提供服务。 Adafruit 为树莓派创建了许多HATsHardware Added on Top),包括我们将用于机器人的 Adafruit 16 通道 PWM /伺服 HAT。

使用此 HAT,用于控制伺服的重复时间脉冲从树莓派卸载到 HAT 上。使用此 HAT,您可以控制多达 16 个伺服。

以下是 HAT 和随附的引脚的照片:

为了我们的目的,我们需要在板上焊接引脚:

  1. 由于我们只使用了两个伺服,因此需要将两个3 引脚伺服引脚焊接到板上。

  2. 焊接2 X 20 引脚引脚。在焊接时,固定板和引脚的一个好方法是使用一些橡皮泥!(确保在焊接时不要让热焊铁太靠近橡皮泥!):

  1. 由于我们将使用来自电机板的电线来为伺服板供电,因此需要将电源引脚焊接到板上。

  2. 我们需要从树莓派访问 GPIO 引脚,因此必须添加另一排引脚。从 40 针引脚排连接器中断开 25 根引脚。将引脚焊接到板上:

第 2 步 - 接线电机

我们需要将电机接线,以便两个电机始终同时且在同一方向旋转:

  1. 切割八根相等长度的电线,并剥离所有电线的两端的一些绝缘:

  1. 将每个电机的端子焊接在一起:

  1. 在端子上应用热缩管以绝缘:

  1. 将每个电机的电线分组并连接起来,使得一个电机顶部的电线连接到另一个电机底部的电线(请参考照片以了解清晰情况):

  1. 为了增加强度和保护,您可以使用热缩管将电线固定在一起(在上一张照片中,使用了黄色热缩管)。

  2. 可以在末端添加延长线(我选择为我的组装添加了延长线,因为电线长度有点短)。稍后添加到末端的蓝色标签胶带将有助于将电机连接到电机驱动器板:

第 3 步 - 组装伺服摄像机支架

有了我们的伺服摄像机支架,T.A.R.A.S 有能力左右摇头和上下运动。这对我们在第十四章中的项目非常有用,即使用 Python 控制机器人车。当您将伺服摄像机支架的零件倒在桌子上时,可能会感到有点令人生畏,不知道如何将其组装成有用的东西。

以下是伺服摄像机支架零件的照片。与其试图命名零件,我会把字母放下来,并参考这些字母进行组装:

要组装伺服摄像机支架,请按以下步骤进行:

  1. 将零件E放入零件A中,使得零件E的突出圆柱朝上:

  1. 翻转并使用包装中最小的螺丝将零件E螺丝固定到零件A上:

  1. 使用小螺丝将伺服螺丝固定在零件D上(请参见下一张照片):

  1. 将零件B放在伺服上,并将零件F插入为其制作的凹槽中。将零件F螺丝固定在位。伺服应能够在连接到零件BF的同时自由上下移动:

  1. 翻转组装好的零件并将另一个伺服插入零件B中:

  1. 将零件C放在伺服上。您可能需要稍微弯曲零件D以使零件C适合:

  1. 翻转组装好的零件并将零件BC螺丝固定在一起:

  1. 将组装好的零件插入零件E中:

  1. 将零件A螺丝固定在组装好的零件底部:

第 4 步 - 附加头部

让我们面对现实吧。除非有某种面孔(向 R2D2 道歉),否则机器人就不是机器人。在这一步中,我们将连接零件以构建 T.A.R.A.S 的头部和面部。

根据 Rethink Robotics 创始人 Rodney Brooks 的说法,机器人并不是为了让它们看起来友好才有脸。机器人的脸被用作人类的视觉线索。例如,如果机器人朝某个方向移动头部,我们可以安全地假设机器人正在分析那个方向的东西。当我们移动 T.A.R.A.S 的头部时,我们向周围的人发出信号,告诉他们 T.A.R.A.S 正在朝那个方向看。

以下是完成头部所需的零件的照片:

现在我们将组装 T.A.R.A.S 的头部。以下是零件清单:

  • A:树莓派摄像头模块

  • B:摄像机支架支撑

  • C:带伺服的组装摄像机支架

  • D:3D 打印支架

  • E:距离传感器

  • F:螺丝

要组装头部,请按以下步骤进行:

  1. 在树莓派摄像头模块和距离传感器上贴上双面泡沫胶带的小块:

  1. 将树莓派摄像头模块和距离传感器粘贴到 3D 打印支架的适当位置(请参考以下照片以获得澄清):

  1. 将组件滑入已组装的摄像头支架上并将其固定在位(请参考以下照片以获得澄清):

  1. 在距离传感器的引脚上加上母对母跳线。在这里,我使用了一个四针连接器连接到 4 个单独的引脚。您也可以使用单独的跳线:

  1. 将组装好的部件转过来,贴上牙齿的贴纸:

步骤 5 - 组装直流电机板

直流电机板位于 T.A.R.A.S 的后部,安装有驱动轮的直流电机驱动器。直流插孔和尾灯 LED 也安装在直流电机板上。我们将从创建尾灯 LED 开始这一步。

以下照片显示了制作尾灯 LED 所需的零件:

以下是零件清单:

  • A:红色跳线(一端必须是女头)

  • B:棕色跳线(一端必须是女头)

  • C:红色 LED

  • D:绿色 LED

  • E:330 欧姆电阻

  • F:热缩管

以下是创建 LED 尾灯的步骤:

  1. 将 330 欧姆电阻焊接到 LED 的阳极(长腿)。

  2. 在连接处加上热缩管以提供强度和绝缘。

  3. 剥开一根红色跳线(确保另一端是女头),并将其焊接到电阻的末端。这是组件的正端:

  1. 在整个电阻上加上热缩管:

  1. 将棕色线焊接到阴极并加上热缩管(在这张照片中,我们展示了一个带有延长棕色线的红色 LED)。这是组件的负端:

  1. 现在我们已经完成了两个尾灯的组装,让我们把直流电机板组装起来。以下是我们需要组装直流电机板的零件的照片:

以下是零件清单:

  • A:红色尾灯

  • B:绿色尾灯

  • C:短电源线

  • D:电机驱动板

  • E:直流电机板(激光切割)

  • F:LED 支架

  • G:直流插孔

  • H:40mm 支架

  • 八颗 M3X10 螺栓(未显示)

  • 四颗 M3 螺母(未显示)

  • 拉链扎带(未显示)

让我们开始组装它:

  1. 用四颗 10mm M3 螺栓将 40mm 支架(H)固定到E上。请参考以下照片以获得正确的方向:

  1. 用四颗 10mm M3 螺栓和螺母将电机驱动板(D)固定到E上。请参考以下照片以获得正确的方向:

这是侧视图:

  1. 将电线C连接到直流插孔(G)端口。确保红线连接到正极,黑线连接到负极。将电线C的另一端连接到电机驱动板(D)。确保红线连接到 VCC,黑线连接到 GND。用拉链扎带将直流插孔(G)固定到直流电机板(E)上。请参考以下照片以获得澄清:

这是接线图:

  1. 或者,您可以使用 AA 电池四组供电。确保使用相同的布线,红线连接到 VCC,黑线连接到 GND:

  1. 将尾灯(B)穿过 LED 孔并穿过 LED 支架(F):

  1. 将 LED 支架(F)推入位。如果孔太紧,使用小锉将孔稍微放大一点(LED 支架应该安装得很紧)。重复操作以安装红色尾灯:

第 6 步 - 安装电机和轮子

在这一步中,我们将开始将零件固定到底盘上。我们将首先固定轮子支架,然后是电机。我们将在这一步中使用 3D 打印的轮子支架。

此步骤所需的零件如下图所示:

以下是零件清单:

  • A:轮子

  • B:电机

  • C:备用轮子支架(激光切割)

  • D:轮子支架(3D 打印)

  • E:机器人车底盘(激光切割)

  • 八颗 M3 10 毫米螺栓(未显示)

  • 八颗 M3 30 毫米螺栓(未显示)

  • 16 颗 M3 螺母(未显示)

让我们开始组装它:

  1. 使用 10 毫米 M3 螺栓和螺母,将每个轮子支架(D)固定到底盘(E)上,使螺栓的头部平躺在轮子支架(D)中。参考以下照片以便澄清:

  1. 使用 30 毫米 M3 螺栓和 M3 螺母,通过使用轮子支架(D)将电机(B)安装到底盘(E)上。确保螺栓的头部平躺。参考以下照片以便澄清:

  1. 或者,您可以使用零件C代替零件D来安装轮子。参考以下照片以便澄清:

  1. 将轮子(A)安装到电机(B)上:

第 7 步 - 连接电机的电线

接下来,我们将安装电机驱动板组件并连接轮子电机的电线:

  1. 首先,使用四颗 M3 10 毫米螺栓将第 5 步中的直流电机板组件固定在底盘顶部。确保将轮子电机的电线穿过中央孔。尾灯 LED 应该安装在机器人车的后部。参考以下照片以便澄清:

  1. 将轮子电机的电线安装到电机驱动板的 OUT1、OUT2、OUT3 和 OUT4 端子块中。右侧电线应连接到 OUT1 和 OUT2,左侧电线应连接到 OUT3 和 OUT4。在这一点上,右侧电线连接到 OUT1 或 OUT2(或左侧电线连接到 OUT3 或 OUT4)并不重要。参考以下照片以便澄清:

第 8 步 - 安装摄像头支架、树莓派和 Adafruit 伺服板

机器人车开始看起来像一辆机器人车。在这一步中,我们将安装摄像头支架(或 T.A.R.A.S 的头部)和树莓派。

我们将从树莓派开始。这是我们必须在如何将树莓派和 Adafruit 伺服板安装到底盘上稍微有些创意的地方。Adafruit 伺服板是一个很棒的小板,但是套件缺少支架,无法防止板的一部分接触树莓派。我发现很难将 M3 螺栓穿过板上的安装孔。我的解决方案是使用 30 毫米的母对公支架将树莓派固定到底盘上,并使用 10 毫米的母对母支架将树莓派与 Adafruit 伺服板分开。

以下是带有一些我收集的支架的树莓派的照片:

以下是上图中的组件:

  • A:15 毫米母对公尼龙支架

  • B:10 毫米母对母尼龙支架

  • C:树莓派

要创建这个电路,请按照以下步骤进行:

  1. 通过将A的一端螺丝拧入另一个端口,创建四个 30 毫米的母对公支架。通过树莓派将B支架螺丝拧入A支架(请参考以下照片以便理解):

  1. 使用四个 10 毫米 M3 螺栓将树莓派固定到底盘上:

现在,让我们安装摄像头支架,连接摄像头,并安装 Adafruit 舵机板:

  1. 使用四个 10 毫米 M3 螺丝和 M3 螺母将摄像头支架安装到底盘的前部(请参考以下照片以便理解):

  1. 通过摄像头模块的排线插入适当的开口到 Adafruit 舵机板(请参考以下照片以便理解):

  1. 将 Adafruit 舵机板固定到树莓派上:

第 9 步 - 安装蜂鸣器和电压分压器

安装在底盘上的最终组件是蜂鸣器和电压分压器。我们需要电压分压器,以便我们可以从距离传感器的回波引脚向树莓派提供 3.3V。对于蜂鸣器,我们使用的是有源蜂鸣器。

有源蜂鸣器在施加直流电压时发出声音。被动蜂鸣器需要交流电压。被动蜂鸣器需要更多的编码。被动蜂鸣器更像是小音箱,因此您可以控制它们发出的声音。

以下是完成此步骤所需的组件:

  • A:迷你面包板

  • B:棕色母对母跳线

  • C:红色母对母跳线

  • D:470 欧姆电阻

  • E:330 欧姆电阻

  • F:有源蜂鸣器

按照以下步骤完成电路:

  1. 为了创建电压分压器,在面包板(A)上将 330 欧姆(E)和 470 欧姆(D)电阻串联:

  1. 将红色跳线(C)连接到蜂鸣器的正极,棕色跳线(B)连接到另一端:

  1. 将蜂鸣器(F)安装在底盘上的适当孔中。使用双面泡沫胶带,将迷你面包板(A)粘贴到底盘的前部(请参考以下照片以便理解):

第 10 步 - 连接 T.A.R.A.S

现在是你一直在等待的部分:连接所有的电线!好吧,也许整理一堆电线并理清它们并不是你的好时光。然而,稍微耐心一点,这一步骤会在你知道之前结束的。

参考以下接线图,将所有电线连接到相应的位置。我们的接线图中不包括电源和电机连接到电机驱动板,因为我们在第 7 步中已经处理了电机的接线。我已经注意到了根据它们的用途分组的电线颜色。请注意,接线图不是按比例绘制的:

要连接 T.A.R.A.S 的电线,执行以下连接:

  • 从 Servo HAT 的引脚五到 L298N(电机板)的 In1

  • 从 Servo HAT 的引脚六到 L298N(电机板)的 In2

  • 从 Servo HAT 的引脚 27 到 L298N(电机板)的 In3

  • 从 Servo HAT 的引脚 22 到 L298N(电机板)的 In4

  • 从 HC-SR04(距离传感器)的 Trig 到 Servo HAT 上的引脚 17

  • 从 HC-SR04(距离传感器)的回波到迷你面包板上 330 欧姆电阻的左侧

  • 从 HC-SR04(距离传感器)的 VCC 到 Servo HAT 上的 5 伏特

  • 从电压分压器的输出到 Servo HAT 上的引脚 18

  • 从 HC-SR04 的 GND 到迷你面包板上 470 欧姆电阻的右侧

  • 从迷你面包板的 GND 到 Servo HAT 上的 GND

  • 从 Servo HAT 电源端子(HAT 左侧)的+5V 到电机驱动板的+5V(使用更粗的电线)

  • 舵机 HAT 电源端子(HAT 左侧)的 GND 连接到电机驱动板上的 GND(使用更粗的电线)

  • 从摄像头支架(水平)底部的舵机到舵机 HAT 上的零号舵机

  • 从摄像头支架(倾斜)中间的舵机到舵机 HAT 上的一号舵机

  • 绿色尾灯上的红线连接到舵机 HAT 上的 20 号引脚

  • 绿色尾灯上的棕色线连接到舵机 HAT 上的 GND

  • 红色尾灯上的红线连接到舵机 HAT 上的 21 号引脚

  • 棕色线连接到舵机 HAT 上的红色尾灯接地

  • 主动蜂鸣器上的红线连接到舵机 HAT 上的 12 号引脚

  • 主动蜂鸣器上的棕色线连接到舵机 HAT 上的 GND

为了启动 T.A.R.A.S,我们将使用两个便携式电源。对于树莓派,我们将使用标准的 USB 便携式电源包。对于电机驱动板和舵机 HAT,我们将使用可充电的 7.4V 电池。安装电池,按照以下步骤进行:

  1. 以下是我们将用于我们的机器人车的两个电池。左边的是用于树莓派的,使用 USB 到 Micro-USB 连接器。右边的是电机驱动板,使用标准的 DC 插头:

  1. 将剥离式粘扣条贴在两个电池和底盘上,并将电池放置在底盘上:

  1. 经过一番必要的整理(清理电线),T.A.R.A.S 已经准备就绪:

学习如何控制机器人车

在第十四章中,使用 Python 控制机器人车,我们将开始编写代码来控制 T.A.R.A.S。在我们开始编写代码之前,看一下如何设置树莓派以访问所需的接口是个好主意。我们应该安装我们需要使用的库来创建控制代码。

配置我们的树莓派

为了确保我们已经启用了机器人车所需的推理,按照以下步骤进行:

  1. 导航到应用程序菜单|首选项|树莓派配置

  2. 单击“接口”选项卡

  3. 启用摄像头、SSH 和 I2C。您可能需要重新启动树莓派:

如果您尚未更改pi用户的默认密码,则在启用 SSH 后可能会收到有关此警告。最好更改默认密码。您可以在树莓派配置工具的系统选项卡下更改它。

Adafruit 舵机 HAT 的 Python 库

为了访问 Adafruit 舵机 HAT,您必须下载并安装库:

  1. git用于从互联网下载 Adafruit 舵机 HAT 库。在 Raspbian 中打开终端并输入以下内容:
sudo apt-get install -y git build-essential python-dev
  1. 如果git已安装,您将收到相应的消息。否则,请继续安装git

  2. 在终端中输入以下内容以下载库:

git clone https://github.com/adafruit/Adafruit_Python_PCA9685.git
  1. 输入以下内容更改目录:
cd Adafruit_Python_PCA9685
  1. 使用以下命令安装库:
sudo python3 setup.py install
  1. 库已成功安装到 Thonny 的工具|管理包中。您应该能看到它在列表中:

摘要

在本章中,我们建造了我们的机器人车 T.A.R.A.S。我们首先概述了零件,然后继续将它们组装在一起。如果您以前从未组装过机器人,那么恭喜!您已正式进入了机器人领域。接下来的路由取决于您。

在本书的其余部分,我们将编程 T.A.R.A.S 执行任务。在第十四章中,使用 Python 控制机器人车,T.A.R.A.S 将被要求参与秘密任务。

问题

  1. 正确还是错误?T.A.R.A.S 代表 Technically Advanced Robots Are Superior。

  2. 主动蜂鸣器和被动蜂鸣器有什么区别?

  3. 正确还是错误?T.A.R.A.S 有摄像头作为眼睛。

  4. 电机驱动板的作用是什么?

  5. Adafruit 舵机 HAT 的目的是什么?

  6. 3D 打印一个轮子支架需要多长时间?

  7. 机器人脸的目的是什么?

  8. Velcro strips are a great way to secure batteries onto the chassis.

第十四章:使用 Python 控制机器人小车

在第十三章中,介绍树莓派机器人小车,我们建造了 T.A.R.A.S 机器人小车。在章节结束时,我们讨论了如何通过代码控制 T.A.R.A.S。在本章中,我们将开始编写代码来实现这一点。

我们将首先编写简单的 Python 代码,然后利用 GPIO Zero 库使车轮向前移动,移动携带摄像头的伺服电机,并点亮机器人小车后面的 LED 灯。

然后,我们将使用类组织我们的代码,然后进一步增强它,让 T.A.R.A.S 执行秘密安全任务。

本章将涵盖以下主题:

  • 查看 Python 代码

  • 修改机器人小车的 Python 代码

  • 增强代码

完成本章所需的知识

如果您跳到本章而没有经历前几章的项目,让我概述一下您完成以下项目所需的技能。当然,我们必须知道如何在 Raspbian OS 中四处走动,以便找到我们的集成开发环境IDE)。

在完成 T.A.R.A.S 的编程后,您可能会倾向于利用新技能与其他构建树莓派机器人的人竞争。Pi Wars (piwars.org/)就是这样一个地方。Pi Wars 是一个在英国剑桥举行的国际机器人竞赛。在一个周末内,最多有 76 支队伍参加基于挑战的机器人竞赛。尽管它被称为 Pi Wars,但您可以放心,您不会带着一箱破碎的零件回来,因为每场比赛都是非破坏性的挑战。查看piwars.org/,或在 YouTube 上搜索 Pi Wars 视频以获取更多信息。

此外,需要对 Python 有一定的了解,因为本章的所有编码都将使用 Python 完成。由于我喜欢尽可能多地使用面向对象的方法,一些面向对象编程OOP)的知识也将帮助您更好地从本章中受益。

项目概述

在本章中,我们将编程 T.A.R.A.S 在桌子周围跳舞并拍照。本章的项目应该需要几个小时才能完成。

入门

要完成这个项目,需要以下内容:

  • 树莓派 3 型号(2015 年或更新型号)

  • USB 电源供应

  • 计算机显示器

  • USB 键盘

  • USB 鼠标

  • 已完成的 T.A.R.A.S 机器人小车套件(参见第十三章,介绍树莓派机器人小车

查看 Python 代码

在某种程度上,我们的机器人小车项目就像是我们在前几章中所做的代码的概述。通过使用 Python 和令人惊叹的 GPIO Zero 库,我们能够从 GPIO 读取传感器数据,并通过向 GPIO 引脚写入数据来控制输出设备。在接下来的步骤中,我们将从非常简单的 Python 代码和 GPIO Zero 库开始。如果您已经完成了本书中的一些早期项目,那么这些代码对您来说将会非常熟悉。

控制机器人小车的驱动轮

让我们看看是否可以让 T.A.R.A.S 移动一点。我们将首先编写一些基本代码来让机器人小车前后移动:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 中打开 Thonny

  2. 单击新图标创建新文件

  3. 将以下代码输入文件:

from gpiozero import Robot
from time import sleep

robot = Robot(left=(5,6), right=(22,27))
robot.forward(0.2)
sleep(0.5)
robot.backward(0.2)
sleep(0.5)
robot.stop()
  1. 将文件保存为motor-test.py

  2. 运行代码

您应该看到机器人小车向前移动0.5秒,然后向后移动相同的时间。如果路上没有障碍物,机器人小车应该回到起始位置。代码相当简单明了;然而,我们现在将对其进行讨论。

我们首先导入我们需要的库:Robotsleep。之后,我们实例化一个名为robotRobot对象,并将其配置为左侧电机的56引脚,右侧电机的2227引脚。之后,我们以0.2的速度将机器人向前移动。为了使机器人移动更快,我们增加这个值。稍作延迟后,我们使用robot.backward(0.2)命令将机器人返回到原始位置。

需要注意的一点是电机的旋转方式,它们会一直旋转,直到使用robot.stop()命令停止。

如果发现电机没有按预期移动,那是因为接线的问题。尝试尝试不同的接线和更改Robot对象的引脚号码(left=(5,6), right=(22,27))。可能需要几次尝试才能搞定。

移动机器人车上的舵机

我们现在将测试舵机。为了做到这一点,我们将从右到左摆动机器人摄像头支架(机器人的头):

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 打开 Thonny

  2. 单击新图标创建一个新文件

  3. 将以下代码输入文件:

import Adafruit_PCA9685 from time import sleep pwm = Adafruit_PCA9685.PCA9685() servo_min = 150
servo_max = 600 while True:
    pwm.set_pwm(0, 0, servo_min)
    sleep(5)
    pwm.set_pwm(0, 0, servo_max)
    sleep(5) 
  1. 将文件保存为servo-test.py

  2. 运行代码

您应该看到机器人头部向右移动,等待5秒,然后向左移动。

在代码中,我们首先导入Adafruit_PCA9685库。在导入sleep函数后,我们创建一个名为pwmPCA9685对象。当然,这是一个使用 Adafruit 代码构建的对象,用于支持 HAT。然后我们分别设置舵机可以移动的最小和最大值,分别为servo_minservo_max

如果您没有得到预期的结果,请尝试调整servo_minservo_max的值。我们在第五章中稍微涉及了一些关于舵机的内容,用 Python 控制舵机

拍照

您可能还记得在以前的章节中使用树莓派摄像头;特别是第九章,构建家庭安全仪表板,我们在那里使用它为我们的安全应用程序拍照。由于 T.A.R.A.S 将是我们可靠的安全代理,它有能力拍照是有意义的。让我们编写一些代码来测试一下我们的机器人车上的摄像头是否工作:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 打开 Thonny

  2. 单击新图标创建一个新文件

  3. 输入以下代码:

from picamera import PiCamera import time camera = PiCamera() camera.capture("/home/pi/image-" + time.ctime() + ".png")   
  1. 将文件保存为camera-test.py

  2. 运行代码

如果一切设置正确,您应该在/home/pi目录中看到一个图像文件,文件名为image,后跟今天的日期。

发出蜂鸣声

我们的安全代理受限于发出噪音以警示我们并吓跑潜在的入侵者。在这一部分,我们将测试安装在 T.A.R.A.S 上的有源蜂鸣器。

旧的英国警察口哨是过去警察官员必须自卫的最早和可信赖的装备之一。英国警察口哨以其独特的声音,允许警官之间进行交流。尽管警察口哨已不再使用,但它的遗产对社会产生了影响,以至于“吹哨人”这个术语至今仍用来指代揭露隐藏的不公正或腐败的人。

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 打开 Thonny

  2. 单击新图标创建一个新文件

  3. 将以下代码输入文件:

from gpiozero import Buzzer
from time import sleep

buzzer = Buzzer(12)
buzzer.on()
sleep(5)
buzzer.off()
  1. 将文件保存为buzzer-test.py

  2. 运行代码

您应该听到蜂鸣器声音持续5秒,然后关闭。

让 LED 闪烁

在 T.A.R.A.S 的背面,我们安装了两个 LED(最好是一个红色和一个绿色)。我们以前使用简单的 GPIO Zero 库命令来闪烁 LED,所以这对我们来说不应该是一个挑战。让我们更进一步,创建可以用来封装 LED 闪烁模式的代码:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 点击新图标创建一个新文件

  3. 输入以下代码:

from gpiozero import LEDBoard
from time import sleep

class TailLights:

    led_lights = LEDBoard(red=21, green=20)

    def __init__(self):
        self.led_lights.on()
        sleep(0.25)
        self.led_lights.off()
        sleep(0.25)

    def blink_red(self, num, duration):
        for x in range(num):
            self.led_lights.red.on()
            sleep(duration)
            self.led_lights.red.off()
            sleep(duration)

    def blink_green(self, num, duration):
        for x in range(num):
            self.led_lights.green.on()
            sleep(duration)
            self.led_lights.green.off()
            sleep(duration)

    def blink_alternating(self, num, duration):
        for x in range(num):
            self.led_lights.red.off()
            self.led_lights.green.on()
            sleep(duration)
            self.led_lights.red.on()
            self.led_lights.green.off()
            sleep(duration)
        self.led_lights.red.off()

    def blink_together(self, num, duration):
        for x in range(num):
            self.led_lights.on()
            sleep(duration)
            self.led_lights.off()
            sleep(duration)

    def alarm(self, num):
        for x in range(num):
            self.blink_alternating(2, 0.25)
            self.blink_together(2, 0.5)

if __name__=="__main__":

    tail_lights = TailLights()
    tail_lights.alarm(20) 
  1. 将文件保存为TailLights.py

  2. 运行代码

你应该看到 LED 显示器闪烁 20 秒。但值得注意的是我们的代码中使用了 GPIO Zero 库的LEDBoard类,如下所示:

led_lights = LEDBoard(red=21, green=20)

在这段代码中,我们从LEDBoard类中实例化一个名为led_lights的对象,并使用redgreen的值来配置它,分别指向2120的 GPIO 引脚。通过使用LEDBoard,我们能够分别或作为一个单元来控制 LED。blink_together方法控制 LED 作为一个单元,如下所示:

def blink_together(self, num, duration):
        for x in range(num):
            self.led_lights.on()
            sleep(duration)
            self.led_lights.off()
            sleep(duration)

我们的代码相当容易理解;然而,还有一些其他事情我们应该指出。当我们初始化TailLights对象时,我们让 LED 短暂闪烁以表示对象已被初始化。这样可以在以后进行故障排除;尽管,如果我们觉得代码是多余的,那么我们以后可以将其删除:

def __init__(self):
        self.led_lights.on()
        sleep(0.25)
        self.led_lights.off()
        sleep(0.25)

保留初始化代码可能会很方便,尤其是当我们想要确保我们的 LED 没有断开连接时(毕竟,谁在尝试连接其他东西时没有断开过某些东西呢?)。要从 shell 中执行此操作,请输入以下代码:

import TailLights
tail_lights = TailLights.TailLights()

你应该看到 LED 闪烁了半秒钟。

修改机器人车 Python 代码

现在我们已经测试了电机、舵机、摄像头和 LED,是时候将代码修改为类,以使其更加统一了。在本节中,我们将让 T.A.R.A.S 跳舞。

移动车轮

让我们从封装移动机器人车轮的代码开始:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 点击新图标创建一个新文件

  3. 将以下代码输入文件中:

from gpiozero import Robot
from time import sleep

class RobotWheels:

    robot = Robot(left=(5, 6), right=(22, 27))

    def __init__(self):
        pass

    def move_forward(self):
        self.robot.forward(0.2)

    def move_backwards(self):
        self.robot.backward(0.2)

    def turn_right(self):
        self.robot.right(0.2)

    def turn_left(self):
        self.robot.left(0.2)

    def dance(self):
        self.move_forward()
        sleep(0.5)
        self.stop()
        self.move_backwards()
        sleep(0.5)
        self.stop()
        self.turn_right()
        sleep(0.5)
        self.stop()
        self.turn_left()
        sleep(0.5)
        self.stop()

    def stop(self):
        self.robot.stop()

if __name__=="__main__":

    robot_wheels = RobotWheels()
    robot_wheels.dance() 
  1. 将文件保存为RobotWheels.py

  2. 运行代码

你应该看到 T.A.R.A.S 在你面前跳了一小段舞。确保连接到 T.A.R.A.S 的电线松动,这样 T.A.R.A.S 就可以做自己的事情。谁说机器人不能跳舞呢?

这段代码相当容易理解。但值得注意的是我们如何从dance方法中调用move_forwardmove_backwardsturn_leftturn_right函数。我们实际上可以参数化移动之间的时间,但这会使事情变得更加复杂。0.5秒的延迟(加上硬编码的速度0.2)似乎非常适合一个不会从桌子上掉下来的跳舞机器人。可以把 T.A.R.A.S 想象成在一个非常拥挤的舞池上,没有太多的移动空间。

但等等,还有更多。T.A.R.A.S 还可以移动头部、点亮灯光并发出一些声音。让我们开始添加这些动作。

移动头部

由于 T.A.R.A.S 上的摄像头连接到头部,因此将头部运动(摄像头支架舵机)与摄像头功能封装起来是有意义的:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 点击新图标创建一个新文件

  3. 将以下代码输入文件中:

from time import sleep
from time import ctime
from picamera import PiCamera
import Adafruit_PCA9685

class RobotCamera:

    pan_min = 150
    pan_centre = 375
    pan_max = 600
    tilt_min = 150
    tilt_max = 200
    camera = PiCamera()
    pwm = Adafruit_PCA9685.PCA9685()

    def __init__(self):
        self.tilt_up()

    def pan_right(self):
        self.pwm.set_pwm(0, 0, self.pan_min)
        sleep(2)

    def pan_left(self):
        self.pwm.set_pwm(0, 0, self.pan_max)
        sleep(2)

    def pan_mid(self):
        self.pwm.set_pwm(0, 0, self.pan_centre)
        sleep(2)

    def tilt_down(self):
        self.pwm.set_pwm(1, 0, self.tilt_max)
        sleep(2)

    def tilt_up(self):
        self.pwm.set_pwm(1, 0, self.tilt_min)
        sleep(2)

    def take_picture(self):
        sleep(2)
        self.camera.capture("/home/pi/image-" + ctime() + ".png")

    def dance(self):
        self.pan_right()
        self.tilt_down()
        self.tilt_up()
        self.pan_left()
        self.pan_mid()

    def secret_dance(self):
        self.pan_right()
        self.tilt_down()
        self.tilt_up()
        self.pan_left()
        self.pan_mid()
        self.take_picture()

if __name__=="__main__":

    robot_camera = RobotCamera()
    robot_camera.dance()
  1. 将文件保存为RobotCamera.py

  2. 运行代码

你应该看到 T.A.R.A.S 把头向右转,然后向下,然后向上,然后全部向左,然后返回到中间并停止。

再次,我们尝试编写我们的代码,使其易于理解。当实例化RobotCamera对象时,init方法确保 T.A.R.A.S 在移动头部之前将头部抬起:

def __init__(self):
    self.tilt_up()

通过调用RobotCamera类,我们将代码结构化为查看机器人车头部舵机和运动的一部分。尽管我们在示例中没有使用摄像头,但我们很快就会使用它。为舵机位置设置的最小和最大值是通过试验和错误确定的,如下所示:

pan_min = 150
pan_centre = 375
pan_max = 600
tilt_min = 150
tilt_max = 200

尝试调整这些值以适应您的 T.A.R.A.S 机器人车的构建。

dancesecret_dance方法使用机器人车头执行一系列动作来模拟跳舞。它们基本上是相同的方法(除了take_picture在最后调用),secret_dance方法使用树莓派摄像头拍照,并以基于日期的名称存储在主目录中。

发出声音

现在 T.A.R.A.S 可以移动身体和头部了,是时候发出一些声音了:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 单击新图标创建新文件

  3. 将以下代码输入文件

from gpiozero import Buzzer
from time import sleep

class RobotBeep:

    buzzer = Buzzer(12)
    notes = [[0.5,0.5],[0.5,1],[0.2,0.5],[0.5,0.5],[0.5,1],[0.2,0.5]]

    def __init__(self, play_init=False):

        if play_init:
            self.buzzer.on()
            sleep(0.1)
            self.buzzer.off()
            sleep(1)

    def play_song(self):

        for note in self.notes:
            self.buzzer.on()
            sleep(note[0])
            self.buzzer.off()
            sleep(note[1])

if __name__=="__main__":

    robot_beep = RobotBeep(True)
  1. 将文件保存为RobotBeep.py

  2. 运行代码

您应该听到 T.A.R.A.S 上的有源蜂鸣器发出短促的蜂鸣声。这似乎是为了做这个而写了很多代码,不是吗?啊,但是等到下一节,当我们充分利用RobotBeep类时。

RobotBeepinit函数允许我们打开和关闭类实例化时听到的初始蜂鸣声。这对于测试我们的蜂鸣器是否正常工作很有用,我们通过在创建robot_beep对象时向类传递True来进行测试:

robot_beep = RobotBeep(True)

notes列表和play_song方法执行类的实际魔术。该列表实际上是一个列表的列表,因为每个值代表蜂鸣器播放或休息的时间:

for note in self.notes:
    self.buzzer.on()
    sleep(note[0])
    self.buzzer.off()
    sleep(note[1])

循环遍历notes列表,查看note变量。我们使用第一个元素作为保持蜂鸣器开启的时间长度,第二个元素作为在再次打开蜂鸣器之前休息的时间量。换句话说,第一个元素确定音符的长度,第二个元素确定该音符与下一个音符之间的间隔。notes列表和play_song方法使 T.A.R.A.S 能够唱歌(尽管没有旋律)。

我们将在下一节中使用play_song方法。

增强代码

这是一个寒冷,黑暗和阴郁的十二月之夜。我们对我们的对手知之甚少,但我们知道他们喜欢跳舞。T.A.R.A.S 被指派到敌人领土深处的一个当地舞厅。在这个晚上,所有感兴趣的人都在那里。如果您选择接受的话,您的任务是编写一个程序,让 T.A.R.A.S 在舞厅拍摄秘密照片。但是,它不能看起来像 T.A.R.A.S 在拍照。T.A.R.A.S 必须跳舞!如果我们的对手发现 T.A.R.A.S 在拍照,那将是糟糕的。非常糟糕!想象一下帝国反击战中的 C3PO 糟糕。

将我们的代码连接起来

因此,我们有能力让 T.A.R.A.S 移动头部和身体,发出声音,发光和拍照。让我们把所有这些放在一起,以便完成任务:

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 单击新图标创建新文件

  3. 将以下内容输入文件:

from RobotWheels import RobotWheels
from RobotBeep import RobotBeep
from TailLights import TailLights
from RobotCamera import RobotCamera

class RobotDance:

    light_show = [2,1,4,5,3,1]

    def __init__(self):

        self.robot_wheels = RobotWheels()
        self.robot_beep = RobotBeep()
        self.tail_lights = TailLights()
        self.robot_camera = RobotCamera()

    def lets_dance_incognito(self):
        for tail_light_repetition in self.light_show:
            self.robot_wheels.dance()
            self.robot_beep.play_song()
            self.tail_lights.alarm(tail_light_repetition)
            self.robot_camera.secret_dance()

if __name__=="__main__":

    robot_dance = RobotDance()
    robot_dance.lets_dance_incognito()

  1. 将文件保存为RobotDance.py

  2. 运行代码

在秘密拍照之前,您应该看到 T.A.R.A.S 执行一系列动作。如果舞蹈结束后检查树莓派home文件夹,您应该会看到六张新照片。

我们代码中值得注意的是light_show列表的使用。我们以两种方式使用此列表。首先,将列表中存储的值传递给我们在RobotDance类中实例化的TailLights对象的alarm方法。我们在lets_dance_incognito方法中使用tail_light_repetition变量,如下所示:

def lets_dance_incognito(self):
    for tail_light_repetition in self.light_show:
        self.robot_wheels.dance()
        self.robot_beep.play_song()
        self.tail_lights.alarm(tail_light_repetition)
        self.robot_camera.secret_dance()

如您在先前的代码中所看到的,TailLights类的变量alarm方法被命名为tail_lights。这将导致 LED 根据tail_light_repetition的值多次执行它们的序列。例如,当将值2传递给alarm方法(light_show列表中的第一个值)时,LED 序列将执行两次。

我们运行lets_dance_incognito方法六次。这基于light_show列表中的值的数量。这是我们使用light_show的第二种方式。为了增加或减少 T.A.R.A.S 执行舞蹈的次数,我们可以从light_show列表中添加或减去一些数字。

当我们在名为robot_cameraRobotCamera对象上调用secret_dance方法时,对于light_show列表中的每个值(在本例中为六),在舞蹈结束后,我们的家目录中应该有六张以日期命名的照片。

T.A.R.A.S 完成舞蹈后,请检查家目录中 T.A.R.A.S 在舞蹈期间拍摄的照片。任务完成!

总结

在本章结束时,您应该熟悉使用 Python 代码控制树莓派驱动的机器人。我们首先通过简单的代码使机器人车上的各种组件工作。在我们确信机器人车确实使用我们的 Python 命令移动后,我们将代码封装在类中,以便更容易使用。这导致了RobotDance类,其中包含对类的调用,这些类又封装了我们机器人的控制代码。这使我们能够使用RobotDance类作为黑匣子,将控制代码抽象化,并使我们能够专注于为 T.A.R.A.S 设计舞步的任务。

在第十五章中,将机器人车的感应输入连接到网络,我们将从 T.A.R.A.S(距离传感器值)中获取感官信息,并将其发布到网络上,然后将 T.A.R.A.S 从桌面上的电线中释放出来,让其自由行动。

问题

  1. 真或假?LEDBoard对象允许我们同时控制许多 LED。

  2. 真或假?RobotCamera对象上的笔记列表用于移动摄像机支架。

  3. 真或假?我们虚构故事中的对手喜欢跳舞。

  4. dancesecret_dance方法之间有什么区别?

  5. 机器人的gpiozero库的名称是什么?

  6. 受老警察哨子启发,给出揭露犯罪行为的行为的术语是什么?

  7. 真或假?封装控制代码是一个毫无意义和不必要的步骤。

  8. TailLights类的目的是什么?

  9. 我们将使用哪个类和方法将机器人车转向右侧?

  10. RobotCamera类的目的是什么?

进一步阅读

学习 GPIO Zero 的最佳参考书之一是 GPIO Zero PDF 文档本身。搜索 GPIO Zero PDF,然后下载并阅读它。

第十五章:将机器人汽车的感应输入连接到网络

为了使我们的机器人汽车 T.A.R.A.S 成为真正的物联网设备,我们必须将 T.A.R.A.S 连接到互联网。在本章中,我们将通过将 T.A.R.A.S 的距离传感器连接到网络,开始将桌面机器人转变为互联网机器人。

本章将涵盖以下主题:

  • 识别机器人汽车上的传感器

  • 使用 Python 读取机器人汽车的感应数据

  • 将机器人汽车的感应数据发布到云端

完成本章所需的知识

要完成本章,您应该已经按照第十三章中详细描述的方式构建了 T.A.R.A.S 机器人汽车。与本书中的其他章节一样,需要具备 Python 的工作知识,以及对面向对象编程的基本理解。

项目概述

本章的项目将涉及将 T.A.R.A.S 的感应距离数据发送到互联网。我们将使用 ThingsBoard 创建一个在线仪表板,该仪表板将在模拟表盘上显示这些距离信息。

这个项目应该需要几个小时来完成。

入门

要完成这个项目,需要以下设备:

  • 一个树莓派 3 型号(2015 年或更新型号)

  • 一个 USB 电源供应

  • 一台电脑显示器

  • 一个 USB 键盘

  • 一个 USB 鼠标

  • 一个完成的 T.A.R.A.S 机器人汽车套件(参见第十三章,介绍树莓派机器人汽车

识别机器人汽车上的传感器

在整本书的过程中,我们使用了一些输入传感器。我们还将这些传感器的数据发布到了互联网上。T.A.R.A.S 使用距离传感器来检测附近的物体,如下图所示:

第一次看到 T.A.R.A.S 时,您可能不知道距离传感器的位置在哪里。在 T.A.R.A.S 和许多其他机器人上,这个传感器位于眼睛位置。

以下是 HC-SR04 距离传感器的照片,即 T.A.R.A.S 上使用的传感器:

如果您在机器人上搜索 HC-SR04 的 Google 图片,您会看到很多使用这个传感器的机器人。由于其低成本和广泛可用性,以及其方便的眼睛外观,它是一个非常受欢迎的选择。

仔细观察 HC-SR04

如前所述,HC-SR04 是一个非常受欢迎的传感器。它易于编程,并且可以从www.aliexpress.com的多个供应商处获得。HC-SR04 提供从 2 厘米到 400 厘米的测量,并且精度在 3 毫米以内。

GPIO Zero 库使得从 HC-SR04 读取数据变得容易。以下图表是使用这个传感器与树莓派连接的接线图:

正如你所看到的,HC-SR04 有四个引脚,其中两个用于信号输入和输出。接线图是我们在第十三章中用来连接 T.A.R.A.S 的子图,介绍树莓派机器人汽车。连接如下:

  • 从 HC-SR04(距离传感器)的 Trig 到树莓派的 17 号引脚

  • 从 HC-SR04 的 Echo(距离传感器)到面包板上 330 欧姆电阻的左侧

  • 从 HC-SR04 的 VCC(距离传感器)到树莓派的 5V

  • 从电压分压器输出到树莓派的 18 号引脚

  • 从 HC-SR04 的 GND 到面包板上 470 欧姆电阻的右侧

触发器是 HC-SR04 的输入,可使用 5V 或 3.3V。回波引脚是输出,设计用于 5V。由于这对我们的树莓派来说有点太多了,我们使用电压分压电路将电压降低到 3.3V。

我们本可以向 T.A.R.A.S 添加更多传感器,使其更加先进,包括线路跟踪传感器、温度传感器、光传感器和 PID 传感器。线路跟踪传感器尤其有趣,因为一个简单的线路可以为 T.A.R.A.S 提供在其安全巡逻任务期间跟随的路线,这是一个非常有用的补充。由于设计已经足够复杂,如果您选择的话,我将留给您添加这个功能。

以下图表概述了线路跟踪传感器的工作原理:

在图表中,您将看到机器人车前面有两个传感器。当机器人车偏离一侧时,其中一个传感器会检测到。在上一个示例中,位置为B的汽车已经向右偏离。左侧传感器会检测到这一点,并且程序通过将机器人车向左转动来进行校正,直到它返回到位置A

使用 Python 读取机器人车的感知数据

虽然我们之前已经介绍过这个,但熟悉(或重新熟悉)HC-SR04 的编程是一个好主意:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 中打开 Thonny。

  2. 点击“新建”创建一个新文件。

  3. 输入以下内容:

from gpiozero import DistanceSensor
from time import sleep

distance_sensor = DistanceSensor(echo=18, trigger=17)

while True:
    print('Distance: ', distance_sensor.distance*100)
    sleep(1) 
  1. 将文件保存为distance-sensor-test.py

  2. 运行代码。

  3. 将手放在距离传感器前面。您应该在 shell 中看到以下内容(取决于您的手离距离传感器有多远):

Distance: 5.05452024001
  1. 当您将手靠近或远离距离传感器时,数值会发生变化。这段代码相当容易理解。distance_sensor = DistanceSensor(echo=18, trigger=17)这一行设置了一个DistanceSensor类类型的distance_sensor对象,并设置了适当的引脚定义。每次调用distance_sensordistance方法时,我们都会获取 HC-SR04 距离物体的距离。为了将值转换为厘米,我们将其乘以 100。

现在我们能够从距离传感器中检索值,让我们修改代码,使其更加面向对象友好:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 中打开 Thonny

  2. 点击“新建”创建一个新文件

  3. 输入以下内容:

from gpiozero import DistanceSensor
from time import sleep

class RobotEyes:

    distance_sensor = DistanceSensor(echo=18, trigger=17)

    def get_distance(self):
        return self.distance_sensor.distance*100

if __name__=="__main__":

    robot_eyes = RobotEyes()

    while True:
        print('Distance: ', robot_eyes.get_distance())
        sleep(1)
  1. 将文件保存为RobotEyes.py

  2. 运行代码

代码应该以完全相同的方式运行。我们所做的唯一的事情就是将它封装在一个类中,以便进行抽象。随着我们编写更多的代码,这将使事情变得更容易。我们不必记住 HC-SR04 连接到哪些引脚,实际上我们也不需要知道我们正在从中获取数据的是一个距离传感器。这段代码在视觉上比以前的代码更有意义。

将机器人车的感知数据发布到云端

在第十章中,发布到 Web 服务,我们设置了一个 ThingsBoard 账户来发布感知数据。如果您还没有这样做,请在www.ThingsBoard.io上设置一个账户(参考第十章中的说明)。

创建一个 ThingsBoard 设备

要将我们的距离传感器数据发布到 ThingsBoard,我们首先需要创建一个 ThingsBoard 设备:

  1. 登录到您的账户demo.thingsboard.io/login

  2. 点击“设备”,然后点击屏幕右下角的大橙色+号:

  1. 在“名称”中输入RobotEyes,将设备类型保留为“默认”,并在“描述”下输入有意义的描述

  2. 点击“添加”

  3. 点击RobotEyes以从右侧滑出菜单

  4. 点击“复制访问令牌”将令牌复制到剪贴板上

  5. 将令牌粘贴到一个文本文件中

对于我们的代码,我们将使用 MQTT 协议。如果 Paho MQTT 库尚未安装在您的树莓派上,请执行以下操作:

  1. 从树莓派主工具栏中打开一个终端应用程序

  2. 输入sudo pip3 install pho-mqtt

您应该看到库已安装。

现在是时候编写代码,将 T.A.R.A.S 的感知数据发布到网络上了。我们将修改我们的RobotEyes类:

  1. 从应用程序菜单|编程|Thonny Python IDE 中打开 Thonny

  2. 点击新建创建一个新文件

  3. 输入以下内容:

from gpiozero import DistanceSensor
from time import sleep
import paho.mqtt.client as mqtt
import json

class RobotEyes:

    distance_sensor = DistanceSensor(echo=18, trigger=17)
    host = 'demo.thingsboard.io'
    access_token='<<access token>>'

    def get_distance(self):
        return self.distance_sensor.distance*100

    def publish_distance(self):
        distance = self.get_distance()
        sensor_data = {'distance': 0}
        sensor_data['distance'] = distance
        client = mqtt.Client()
        client.username_pw_set(self.access_token)
        client.connect(self.host, 1883, 20)
        client.publish('v1/devices/me/telemetry',
             json.dumps(sensor_data), 1)
        client.disconnect()

if __name__=="__main__":

    robot_eyes = RobotEyes()   
    while True:
        print('Distance: ', robot_eyes.get_distance())
        robot_eyes.publish_distance()
        sleep(5) 
  1. 确保将访问令牌从文本文件粘贴到access_token变量中

  2. 将文件保存为RobotEyesIOT.py

  3. 运行代码

您应该在 shell 中看到distance值,就像以前一样。但是,当您转到 ThingsBoard 并单击最新遥测时,您应该看到相同的值,如下所示:

我们在这里所取得的成就,就像第十章中一样,发布到 Web 服务,成功地将我们的距离传感器信息传输到了互联网。现在,我们可以从世界上任何地方看到物体离我们的机器人车有多近。在上一张屏幕截图中,我们可以看到有某物离我们 3.801 厘米远。

再次,我们尽可能地使代码自解释。但是,我们应该指出类的publish_distance方法:

def publish_distance(self):
 distance = self.get_distance()
 sensor_data = {'distance': 0}
 sensor_data['distance'] = distance
 client = mqtt.Client()
 client.username_pw_set(self.access_token)
 client.connect(self.host, 1883, 20)
 client.publish('v1/devices/me/telemetry',
 json.dumps((sensor_data), 1)
 client.disconnect()

在这种方法中,我们首先创建了一个名为distance的变量,我们用我们的类get_distance方法中的实际距离信息填充它。创建了一个名为sensor_data的 Python 字典对象,并用它来存储distance值。然后,我们创建了一个名为client的 MQTT 客户端对象。我们将密码设置为我们从 ThingsBoard 复制的access_token,然后使用标准的 ThingsBoard 样板代码进行连接。

client.publish方法通过json.dumps方法将我们的sensor_data发送到 ThingsBoard。然后,我们断开client以关闭连接。

现在,让我们使用我们的距离传感数据创建一个仪表板小部件:

  1. 在 ThingsBoard 中,单击最新遥测,并在列表中的distance值旁边选中复选框:

  1. 点击在小部件上显示

  2. 在当前包中,从下拉菜单中选择模拟表,如下所示:

  1. 选择最后一个小部件:

  1. 点击顶部的添加到仪表板

  2. 创建名为RobotEyes的新仪表板,并选中打开仪表板框:

  1. 点击添加

  2. 恭喜!我们现在已经为 T.A.R.A.S 的感知距离信息创建了一个物联网仪表板小部件。有了这个,我们可以全屏查看信息:

摘要

在本章中,我们通过将距离数据发布到互联网,将 T.A.R.A.S 变成了一个真正的物联网物体。通过将我们的代码封装到一个名为RobotEyes的类中,我们可以忘记我们正在处理距离传感器,只需专注于 T.A.R.A.S 的眼睛表现得像声纳一样。

通过 ThingsBoard 中的演示平台,我们能够编写代码,将 T.A.R.A.S 的距离信息发送到仪表板小部件以供显示。如果我们真的想要创造性地做,我们可以通过舵机连接一个实际的模拟设备,并以这种方式显示距离信息(就像我们在第六章中所做的那样,使用舵机控制代码来控制模拟设备)。在第十六章中,使用 Web 服务调用控制机器人车,我们将更进一步,开始从互联网上控制 T.A.R.A.S。

问题

  1. 连接 HC-SR04 到树莓派时,为什么要使用电压分压电路?

  2. 正确还是错误?T.A.R.A.S 通过声纳来看。

  3. ThingsBoard 中的设备是什么?

  4. 正确还是错误?我们的类RobotEyes封装了 T.A.R.A.S 上使用的树莓派摄像头模块。

  5. RobotEyes.publish_distance方法是做什么的?

  6. 真的还是假的?我们需要使用 MQTT 的库在 Raspbian 上预先安装。

  7. 为什么我们将类命名为RobotEyes而不是RobotDistanceSensor

  8. 真的还是假的?将样板代码封装在一个类中会使代码更难处理。

  9. 真的还是假的?GPIO Zero 库不支持距离传感器。

  10. RobotEyes.pyRobotEyesIOT.py之间有什么区别?

进一步阅读

ThingsBoard 平台的一个很好的指导来源是它自己的网站。访问www.thingsboard.io/docs/guides获取更多信息。

第十六章:使用 Web 服务调用控制机器人车

有一天,无人驾驶汽车将主导我们的街道和高速公路。尽管感应信息和控制算法将位于汽车本身,但我们将有能力(并且可能会成为立法要求)从其他地方控制汽车。控制无人驾驶汽车将需要将汽车的感应信息以速度、GPS 位置等形式发送到控制站。相反,控制站的信息将以交通和方向等形式发送到汽车。

在本章中,我们将探讨从 T.A.R.A.S 发送感应信息和接收 T.A.R.A.S 控制信息的两个方面。

本章将涵盖以下主题:

  • 从云端读取机器人车的数据

  • 使用 Python 程序通过云端控制机器人车

完成本章所需的知识

要完成本章,您应该有一个完整的 T.A.R.A.S 机器人车,详细描述在第十三章中,介绍树莓派机器人车。与本书中的其他章节一样,需要具备 Python 的工作知识,以及对面向对象编程的基本理解。

项目概述

本章的项目将涉及通过互联网与 T.A.R.A.S 进行通信。我们将深入研究在第十五章中创建的仪表板模拟表,然后在仪表板上创建控制 T.A.R.A.S 的开关。这些项目应该需要大约 2 小时才能完成。

技术要求

要完成此项目,需要以下内容:

  • 一个树莓派 3 型号(2015 年或更新型号)

  • 一个 USB 电源适配器

  • 一台电脑显示器

  • 一个 USB 键盘

  • 一个 USB 鼠标

  • 一个完整的 T.A.R.A.S 机器人车套件(参见第十三章,介绍树莓派机器人车

从云端读取机器人车的数据

在第十五章中,将机器人车的感应输入连接到网络,我们能够使用网站thingsboard.io/将距离感应数据发送到云端。最后,我们展示了一个显示距离数值的模拟仪表。在本节中,我们将深入研究模拟小部件并进行自定义。

改变距离表的外观

这是我们改变距离表外观的方法:

  1. 登录您的 ThingsBoard 账户

  2. 点击 DASHBOARDS

  3. 点击 ROBOTEYES 标题

  4. 单击屏幕右下角的橙色铅笔图标

  5. 您会注意到距离模拟表已经改变(见下面的屏幕截图)

  6. 首先,表盘右上角有三个新图标

  7. 右下角的颜色也变成了浅灰色

  8. 您可以通过将鼠标悬停在右下角来调整小部件的大小

  9. 您也可以将小部件移动到仪表板上

  10. 右上角的 X 允许您从仪表板中删除此小部件

  11. 带有下划线箭头的图标允许您将小部件下载为.json文件。此文件可用于将小部件导入 ThingsBoard 上的另一个仪表板

  12. 单击小部件上的铅笔图标会产生一个从右侧滑出的菜单:

  1. 如前面的屏幕截图所示,菜单选项为 DATA、SETTINGS、ADVANCED 和 ACTION。默认为 DATA

  2. 点击 SETTINGS 选项卡

  3. 在标题下,将名称更改为RobotEyes

  1. 点击显示标题复选框

  2. 点击背景颜色下的白色圆圈:

  1. 您将看到颜色选择对话框:

  1. 将顶部更改为rgb(50,87,126)

  2. 点击右上角的橙色复选框以接受更改

  3. 您会注意到距离表有一些外观上的变化(请参见以下屏幕截图):

更改距离表上的范围

看着距离模拟表,很明显,对于我们的应用程序来说,有负数并没有太多意义。让我们将范围更改为0100

  1. 点击小部件上的铅笔图标

  2. 点击“高级”选项卡

  3. 将最小值更改为0,将最大值更改为100

  1. 点击右上角的橙色复选框以接受对小部件的更改

  2. 关闭 ROBOTEYES 对话框

  3. 点击右下角的橙色复选框以接受对仪表板的更改

  4. 您会注意到距离模拟表现在显示范围为0100

在您的帐户之外查看仪表板

对于我们的最后一个技巧,我们将在我们的帐户之外显示我们的仪表板(我们在第十章中也这样做,发布到 Web 服务)。这也允许我们将我们的仪表板发送给朋友。那么,为什么我们要在帐户之外查看我们的仪表板呢?物联网的核心概念是我们可以从一个地方获取信息并在其他地方显示,也许是在世界的另一边的某个地方。通过使我们的仪表板在我们的帐户之外可访问,我们允许在任何地方设置仪表板,而无需共享我们的帐户信息。想象一下世界上某个地方有一块大屏幕,屏幕的一小部分显示我们的仪表板。从 T.A.R.A.S 显示距离信息可能对许多人来说并不是很感兴趣,但重要的是概念。

要分享我们的仪表板,请执行以下操作:

  1. 在 ThingsBoard 应用程序中,点击“仪表板”选项

  2. 点击 RobotEyes 仪表板下的中间图标:

  1. 您将看到类似以下的对话框(URL 已部分模糊处理):

  1. 点击 URL 旁边的图标将 URL 复制到剪贴板

  2. 要测试 URL,请将其粘贴到计算机上的完全不同的浏览器中(或将其发送给朋友并让他们打开)

  3. 您应该能够看到我们的距离模拟表的仪表板

使用 Python 程序通过云控制机器人车

能够在仪表板中看到传感器数据是非常令人印象深刻的。但是,如果我们想要从我们的仪表板实际控制某些东西怎么办?在本节中,我们将做到这一点。我们将首先构建一个简单的开关来控制 T.A.R.A.S 上的 LED。然后,我们将扩展此功能,并让 T.A.R.A.S 通过互联网上的按钮按下来跳舞。

让我们首先将仪表板的名称从RobotEyes更改为RobotControl

  1. 在 ThingsBoard 应用程序中,点击“仪表板”选项

  2. 点击 RobotEyes 仪表板下的铅笔图标:

  1. 点击橙色铅笔图标

  2. 将瓷砖从RobotEyes更改为RobotControl

  1. 点击橙色复选框以接受更改

  2. 退出侧边对话框

现在让我们从 ThingsBoard 仪表板上控制 T.A.R.A.S 上的 LED。

向我们的仪表板添加一个开关

为了控制 LED,我们需要创建一个开关:

  1. 点击 RobotControl 仪表板

  2. 点击橙色铅笔图标

  3. 点击+图标

  4. 点击“创建新小部件”图标

  5. 选择“控制小部件”并点击“切换控制”:

  1. 在目标设备下,选择 RobotControl

  2. 点击“设置”选项卡:

  1. 将标题更改为Green Tail Light,然后点击显示标题

  2. 点击高级选项卡

  3. 将 RPC 设置值方法更改为toggleGreenTailLight

  1. 点击橙色的勾号图标以接受对小部件的更改

  2. 关闭侧边对话框

  3. 点击橙色的勾号图标以接受对仪表板的更改

那么,我们刚刚做了什么?我们在我们的仪表板上添加了一个开关,它将发布一个名为toggleGreenTailLight的方法,该方法将返回一个值,要么是true,要么是false(默认返回值为this is a switch)。

既然我们有了开关,让我们在树莓派上编写一些代码来响应它。

控制 T.A.R.A.S 上的绿色 LED

要控制 T.A.R.A.S 上的绿色 LED,我们需要编写一些代码到 T.A.R.A.S 上的树莓派。我们需要我们仪表板的访问令牌(参见第十五章,将机器人汽车的感应输入连接到网络,关于如何获取):

  1. 从应用程序菜单中打开 Thonny | 编程 | Thonny Python IDE

  2. 点击新建图标创建一个新文件

  3. 输入以下内容:

import paho.mqtt.client as mqtt
from gpiozero import LED
import json

THINGSBOARD_HOST = 'demo.thingsboard.io' ACCESS_TOKEN = '<<access token>>'
green_led=LED(21)

def on_connect(client, userdata, rc, *extra_params):
   print('Connected with result code ' + str(rc))
    client.subscribe('v1/devices/me/rpc/request/+')

def on_message(client, userdata, msg):
    data = json.loads(msg.payload.decode("utf-8")) 

    if data['method'] == 'toggleGreenTailLight':
        if data['params']:
            green_led.on()
        else:
            green_led.off()

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set(ACCESS_TOKEN)
client.connect(THINGSBOARD_HOST, 1883, 60)

client.loop_forever()
  1. 将文件保存为control-green-led-mqtt.py

  2. 运行代码

  3. 返回我们的 ThingsBoard 仪表板(如果您一直在 T.A.R.A.S 上的树莓派之外的计算机上使用,现在是一个好时机)

  4. 点击开关以打开它

  5. 您应该看到 T.A.R.A.S 上的绿色 LED 随开关的打开和关闭而打开和关闭

那么,我们刚刚做了什么?使用从 ThingsBoard 网站获取的样板代码,我们构建了一个消息查询遥测传输MQTT)客户端,该客户端监听仪表板,并在接收到toggleGreenTailLight方法时做出响应。我们通过在on_connect方法中订阅'v1/devices/me/rpc/request/+'来实现这一点。我们在第十章中也使用了 MQTT,发布到网络服务。然而,由于这段代码几乎只是 MQTT 代码,让我们更仔细地研究一下。

MQTT 是一种基于发布者订阅者方法的轻量级消息传递协议,非常适合在物联网中使用。理解发布者和订阅者的一个好方法是将它们与过去的报纸联系起来。发布者是制作报纸的实体;订阅者是购买和阅读报纸的人。发布者不知道,甚至不必知道,为了印刷报纸有多少订阅者(不考虑出版成本)。想象一下每天都会出版的巨大报纸,不知道有多少人会购买他们的报纸。因此,发布者可以有很多订阅者,反之亦然,订阅者可以订阅很多发布者,就像读者可以阅读很多不同的报纸一样。

我们首先导入我们代码所需的库:

import paho.mqtt.client as mqtt
from gpiozero import LED
import json

THINGSBOARD_HOST = 'demo.thingsboard.io'
ACCESS_TOKEN = '<<access token>>'
green_led=LED(21)

这里需要注意的是jsonpho.mqtt.client库,这些库是与 MQTT 服务器通信所需的。THINGSBOARD_HOSTACCESS_TOKEN是连接到正确服务器和服务所需的标准变量。当然,还有GPIO Zero LED类,它将green_led变量设置为 GPIO 引脚21(这恰好是 T.A.R.A.S 上的绿色尾灯)。

on_connect方法打印出连接信息,然后订阅将我们连接到来自我们 ThingsBoard 仪表板的rpc方法的服务:

def on_connect(client, userdata, rc, *extra_params):
    print('Connected with result code ' + str(rc))
    client.subscribe('v1/devices/me/rpc/request/+')

正是on_message方法使我们能够真正修改我们的代码以满足我们的目的:

def on_message(client, userdata, msg):
    data = json.loads(msg.payload.decode("utf-8")) 

    if data['method'] == 'toggleGreenTailLight':
        if data['params']:
            green_led.on()
        else:
            green_led.off()

我们首先从我们的msg变量中收集data,然后使用json.loads方法将其转换为json文件。method声明on_message(client, userdata, msg),再次是来自 ThingsBoard 网站的标准样板代码。我们真正关心的只是获取msg的值。

第一个if语句,if data['method'] == 'toggleGreenTailLight',检查我们的msg是否包含我们在 ThingsBoard 仪表板上设置的toggleGreenTailLight方法。一旦我们知道msg包含这个方法,我们使用if data['params']提取data中的其他键值对,以检查是否有True值。换句话说,调用on_message方法返回的json文件看起来像{'params': True, 'method': 'toggleGreenTailLight'}。这基本上是一个包含两个键值对的 Python 字典。这可能看起来令人困惑,但最简单的想法是将其想象成一个json版本的方法(toggleGreenTailLight)和一个返回值(True)。

真正理解发生了什么的一种方法是在on_message方法中添加一个print语句来print data,就在data = json.loads(msg.payload.decode("utf-8"))之后。因此,该方法看起来像以下内容:

def on_message(client, userdata, msg):
    data = json.loads(msg.payload.decode("utf-8")) 
    print(data)
    .
    .
    . 

当从params返回的值为True时,我们简单地使用标准的 GPIO Zero 代码打开 LED。当从params返回的值不是True(或False,因为只有两个可能的值)时,我们关闭 LED。

通过使用互联网看到 LED 开关是相当令人印象深刻的。然而,这还不够。让我们利用我们在之前章节中使用的一些代码,让 T.A.R.A.S 跳舞。这一次,我们将通过互联网让它跳舞。

使用互联网让 T.A.R.A.S 跳舞

要让 T.A.R.A.S 再次跳舞,我们需要确保第十四章中的代码使用 Python 控制机器人车与我们将要编写的代码在同一个目录中。

我们将从在我们的仪表板上创建一个跳舞开关开始:

  1. 按照之前的步骤 1 到 9,在仪表板下添加一个开关来创建一个开关

  2. 将标题更改为 Dance Switch 并点击显示标题

  3. 点击高级选项卡

  4. RPC set value method更改为dance

  5. 点击橙色的勾号图标以接受对小部件的更改

  6. 关闭侧边对话框

  7. 点击橙色的勾号图标以接受对仪表板的更改

现在我们有了开关,让我们修改我们的代码:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 打开 Thonny

  2. 点击新图标创建一个新文件

  3. 输入步骤 4 中的以下内容:

import paho.mqtt.client as mqtt
import json
from RobotDance import RobotDance

THINGSBOARD_HOST = 'demo.thingsboard.io'
ACCESS_TOKEN = '<<access token>>'
robot_dance = RobotDance()

def on_connect(client, userdata, rc, *extra_params):
    print('Connected with result code ' + str(rc))
    client.subscribe('v1/devices/me/rpc/request/+')

def on_message(client, userdata, msg):
    data = json.loads(msg.payload.decode("utf-8")) 

    if data['method'] == 'dance':
        if data['params']:
            robot_dance.lets_dance_incognito()

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set(ACCESS_TOKEN)
client.connect(THINGSBOARD_HOST, 1883, 60)

client.loop_forever()
  1. 将文件保存为internet-dance.py

  2. 运行代码

现在去仪表板上打开跳舞开关(不幸的是,它是一个开关而不是一个按钮)。T.A.R.A.S 应该开始跳舞,就像在第十四章中一样,使用 Python 控制机器人车

那么,我们刚刚做了什么?嗯,我们拿了简单的代码,稍微修改了一下,通过面向对象编程的力量,我们能够让 T.A.R.A.S 跳舞,而无需更改甚至浏览我们旧的RobotDance代码(难道 OOP 不是自从你认为最好的东西以来最好的东西吗?)。

对于 MQTT 代码,我们所要做的就是在RobotDance类中添加import,去掉多余的 GPIO Zero 导入,去掉对 LED 的任何引用(因为这会引起冲突),然后修改我们的on_message方法以查找dance作为方法。

RobotDance类类型的robot_dance对象完成了所有工作。当我们在这个对象上调用lets_dance_incognito方法时,它会启动RobotWheelsRobotBeepTailLightsRobotCamera类中用于移动的方法。最终结果是通过互联网上的开关让 T.A.R.A.S 跳舞的方法。

摘要

在本章中,我们进一步研究了我们用于距离传感信息的仪表盘模拟表。在更改范围并将其公开之前,我们对其进行了美学修改。然后,我们将注意力转向通过互联网控制 T.A.R.A.S。通过使用一个简单的程序,我们能够通过仪表盘开关打开 T.A.R.A.S 上的绿色 LED。我们利用这些知识修改了我们的代码,通过另一个仪表盘开关使 T.A.R.A.S 跳舞。

在第十七章 构建 JavaScript 客户端中,我们将继续编写一个 JavaScript 客户端,通过互联网控制 T.A.R.A.S。

问题

  1. 无人驾驶汽车需要从中央站获取什么类型的信息?

  2. 真/假?在 ThingsBoard 仪表盘中无法更改小部件的背景颜色。

  3. 如何更改仪表盘模拟表的范围?

  4. 真/假?从行print(data)返回的信息无法被人类阅读。

  5. 我们从RobotDance类中调用哪个方法来使 T.A.R.A.S 跳舞?

  6. 真/假?我们需要使用的处理json数据的库叫做jason

  7. 我们如何在仪表盘上创建一个开关?

  8. 真/假?T.A.R.A.S 上的绿色 LED 连接到 GPIO 引脚 14。

  9. 真/假?一个发布者只能有一个订阅者。

  10. 使用on_message方法从msg返回多少个键值对?

进一步阅读

由于我们只是简单地涉及了 ThingsBoard,查看他们的文档是个好主意,网址是thingsboard.io/docs/guides/

第十七章:构建 JavaScript 客户端

让我们面对现实吧。如果没有互联网,我们真的不会有物联网。JavaScript,连同 HTML 和 CSS,是互联网的核心技术之一。物联网的核心是设备之间通信的协议 MQTT。

在这一章中,我们将把注意力从 Python 转移到使用 JavaScript 构建 JavaScript 客户端以订阅 MQTT 服务器上的主题。

本章将涵盖以下主题:

  • 介绍 JavaScript 云库

  • 使用 JavaScript 连接到云服务

项目概述

我们将从创建一个简单的 JavaScript 客户端开始这一章,该客户端连接到 MQTT Broker(服务器)。我们将向 MQTT Broker 发送一条测试消息,然后让该消息返回到我们创建 JavaScript 客户端的同一页。然后我们将从 Raspberry Pi 发布一条消息到我们的 MQTT Broker。

完成本章应该需要几个小时。

入门

要完成这个项目,需要以下内容:

  • Raspberry Pi 3 型号(2015 年或更新型号)

  • USB 电源适配器

  • 计算机显示器

  • USB 键盘

  • USB 鼠标

  • 用于编写和执行 JavaScript 客户端程序的单独计算机

介绍 JavaScript 云库

让我们首先介绍一下 JavaScript 云库的背景。JavaScript 自互联网诞生以来就存在(1995 年,举例而言)。它已经成为一种可以将 HTML 网页转变为完全功能的桌面等效应用程序的语言。就我个人而言,我发现 JavaScript 是最有用的编程语言之一(当然,除了 Python)。

JavaScript 于 1995 年发布,旨在与当时最流行的网络浏览器 Netscape Navigator 一起使用。它最初被称为 livescript,但由于在 Netscape Navigator 浏览器中使用和支持 Java,名称被更改为 JavaScript。尽管语法相似,但 Java 和 JavaScript 实际上与彼此无关——这是一个令人困惑的事实,直到今天仍然存在。

谷歌云

通过google-api-javascript-client,我们可以访问谷歌云服务。具体来说,我们可以访问谷歌计算引擎,这是谷歌云平台的一个组件。通过谷歌计算引擎,我们可以通过按需虚拟机访问运行 Gmail、YouTube、谷歌搜索引擎和其他谷歌服务的基础设施。如果这听起来像是能让你的朋友印象深刻的技术术语,你可能需要更深入地了解这个 JavaScript 库。您可以在这里了解更多关于google-api-javascript-client的信息:cloud.google.com/compute/docs/tutorials/javascript-guide

AWS SDK for JavaScript

AWS SDK for JavaScript in Node.js 提供了 AWS 服务的 JavaScript 对象。这些服务包括 Amazon S3、Amazon EC2、Amazon SWF 和 DynamoDB。此库使用 Node.js 运行时环境。您可以在这里了解更多关于这个库的信息:aws.amazon.com/sdk-for-node-js/

Node.js 于 2009 年 5 月发布。最初的作者是 Ryan Dhal,目前由 Joyent 公司开发。Node.js 允许在浏览器之外执行 JavaScript 代码,从而使其成为一种 JavaScript 无处不在的技术。这使 JavaScript 可以在服务器端和客户端用于 Web 应用程序。

Eclipse Paho JavaScript 客户端

Eclipse Paho JavaScript 客户端库是一个面向 JavaScript 客户端的 MQTT 基于浏览器的库。Paho 本身是用 JavaScript 编写的,可以轻松地插入到 Web 应用程序项目中。Eclipse Paho JavaScript 客户端库使用 Web 套接字连接到 MQTT Broker。我们将在本章的项目中使用这个库。

使用 JavaScript 连接到云服务

对于我们的项目,我们将构建一个 JavaScript 客户端并将其连接到 MQTT Broker。我们将发布订阅名为testtopic。然后,我们将在树莓派上编写一个小的简单程序来发布到名为 test 的主题。这段代码将演示使用 MQTT 发送和接收消息是多么容易。

请查看以下图表,了解我们将通过此项目实现的内容:

设置 CloudMQTT 帐户

第一步是设置 MQTT Broker。我们可以通过在本地安装 Mosquitto 平台(www.mosquitto.org)来完成此操作。相反,我们将使用网站www.cloudmqtt.com设置基于云的 MQTT Broker。

要设置帐户:

  1. 在浏览器中,导航到www.cloudmqtt.com.

  2. 在右上角点击登录。

  3. 在创建帐户框中,输入您的电子邮件地址:

  1. 您将收到一封发送到该电子邮件地址的电子邮件,要求您确认。您可以通过单击电子邮件中的确认电子邮件按钮来完成确认过程。

  2. 然后您将进入一个页面,需要输入密码。选择密码,确认密码,然后按提交:

  1. 然后您将进入实例页面。这是我们将创建 MQTT Broker 实例以发送和发布 MQTT 消息的地方。

设置 MQTT Broker 实例

现在我们已经设置了 CloudMQTT 帐户,是时候创建一个用于我们应用程序的实例了:

  1. 从实例页面,单击标有创建新实例的大绿色按钮。

  2. 您将看到以下页面:

  1. 在名称框中,输入T.A.R.A.S(我们将将 MQTT Broker 实例命名为此,因为我们将考虑此 Broker 是 T.A.R.A.S 机器人汽车的一部分)。

  2. 在计划下拉菜单中,选择 Cute Cat(这是用于开发目的的免费选项)。

  3. 点击绿色的选择区域按钮。

  4. 根据您所在的世界位置,选择一个靠近您地理位置的区域。由于我位于加拿大,我将选择 US-East-1(北弗吉尼亚):

  1. 点击绿色的确认按钮。

  2. 您将看到确认新实例页面。在点击绿色的确认实例按钮之前,请查看此信息:

  1. 您应该看到 T.A.R.A.S 实例在列表中的实例列表中:

编写 JavaScript 客户端代码

这是我在我的帐户上设置的 T.A.R.A.S 实例的屏幕截图。请注意列表中的值。这些值来自我的实例,您的值将不同。我们将在编写 JavaScript 客户端时使用这些值:

要编写我们的 JavaScript 客户端代码,我们应该使用 T.A.R.A.S 上的树莓派以外的计算机。您可以使用任何您喜欢的操作系统和 HTML 编辑器。我使用 macOS 和 Visual Studio Code 编写了我的 JavaScript 客户端代码。您还需要 Paho JavaScript 库:

  1. 转到 Eclipse Paho 下载站点projects.eclipse.org/projects/technology.paho/downloads

  2. 点击 JavaScript 客户端链接。它将以JavaScript 客户端的名称标记,后跟版本号。在撰写本文时,版本号为 1.03。

  3. JavaScript 客户端库将以paho.javascript-1.0.3的 ZIP 文件形式下载。解压文件。

  4. 我们需要在计算机上创建一个用作项目文件夹的文件夹。在计算机上创建一个新文件夹,并将其命名为MQTT HTML Client

  5. MQTT HTML Client文件夹内创建一个名为scripts的子文件夹。

  6. 将解压后的paho.javascript-1.0.3文件夹拖放到MQTT HTML Client文件夹中。

  7. MQTT HTML Client文件夹内的目录结构应如下所示:

现在,是时候编写代码了。我们将尽可能简化我们的代码,以便更好地理解 MQTT 如何与 JavaScript 配合使用。我们的客户端代码将包括两个文件,一个 HTML 页面和一个.js(JavaScript)文件。让我们从创建 HTML 页面开始:

  1. 使用您喜欢的 HTML 编辑器,创建一个名为index.html的文件并保存到项目根目录。

  2. 您的project文件夹应该如下所示:

  1. index.html文件中输入以下内容:
<!DOCTYPE html>
<html>

<head>
 <title>MQTT Message Client</title>
 <script src="paho.javascript-1.0.3/paho-mqtt.js" type="text/javascript"></script>
 <script src="scripts/index.js" type='text/javascript'></script>
</head>

<body>

 <h2>MQTT Message Client</h2>
 <button onclick="sendTestData()">
 <h4>Send test message</h4>
 </button>

 <button onclick="subscribeTestData()">
 <h4>Subscribe to test</h4>
 </button>

 <div>
 <input type="text" id="messageTxt" value="Waiting for MQTT message" size=34 />
 </div>

</body>

</html>
  1. 保存对index.html的更改。

  2. 我们在这里做的是创建一个简单的 HTML 页面,并导入了两个 JavaScript 库,Paho JavaScript 库和一个名为index.js的文件,我们还没有创建:

<script src="paho.javascript-1.0.3/paho-mqtt.js" type="text/javascript"></script>
<script src="scripts/index.js" type='text/javascript'></script>
  1. 然后,我们需要创建两个按钮;在顶部按钮上,我们将onclick方法设置为sendTestData。在底部按钮上,我们将onclick方法设置为subscribeTestData。这些方法将在我们编写的 JavaScript 文件中创建。为简单起见,我们不给这些按钮分配 ID 名称,因为我们不会在我们的 JavaScript 代码中引用它们:
<button onclick="sendTestData()">
        <h4>Send test Message</h4>
</button>
<button onclick="subscribeTestData()">
        <h4>Subscribe to test</h4>
</button>
  1. 我们在index.html页面中将创建的最后一个元素是一个文本框。我们为文本框分配了一个idmessageTxt和一个值为Waiting for MQTT message
<div>
    <input type="text" id="messageTxt" value="Waiting for MQTT message" size=34 />
</div>
  1. 如果我们将index.html加载到浏览器中,它将如下所示:

运行代码

在运行客户端代码之前,我们需要创建一个 JavaScript 文件,该文件将提供我们需要的功能:

  1. 使用 HTML 编辑器,在我们的项目目录中的scripts文件夹中创建一个名为index.js的文件并保存。

  2. 将以下代码添加到index.js并保存。用您的实例中的值替换ServerUserPasswordWebsockets Port(分别显示为"m10.cloudmqtt.com"38215"vectydkb""ZpiPufitxnnT"):

function sendTestData() {
 client = new Paho.MQTT.Client
 ("m10.cloudmqtt.com", 38215, "web_" + 
 parseInt(Math.random() * 100, 10));

 // set callback handlers
 client.onConnectionLost = onConnectionLost;

 var options = {
 useSSL: true,
 userName: "vectydkb",
 password: "ZpiPufitxnnT",
 onSuccess: sendTestDataMessage,
 onFailure: doFail
 }

 // connect the client
 client.connect(options);
}

// called when the client connects
function sendTestDataMessage() {
 message = new Paho.MQTT.Message("Hello from JavaScript 
 client");
 message.destinationName = "test";
 client.send(message);
}

function doFail() {
 alert("Error!");
}

// called when the client loses its connection
function onConnectionLost(responseObject) {
 if (responseObject.errorCode !== 0) {
 alert("onConnectionLost:" + responseObject.errorMessage);
 }
}

// called when a message arrives
function onMessageArrived(message) {
 document.getElementById('messageTxt').value = message.payloadString; 
}

function onsubsribeTestDataSuccess() {
 client.subscribe("test");
 alert("Subscribed to test");
}

function subscribeTestData() {
 client = new Paho.MQTT.Client
 ("m10.cloudmqtt.com", 38215, "web_" + 
 parseInt(Math.random() * 100, 10));

 // set callback handlers
 client.onConnectionLost = onConnectionLost;
 client.onMessageArrived = onMessageArrived;

 var options = {
 useSSL: true,
 userName: "vectydkb",
 password: "ZpiPufitxnnT",
 onSuccess: onsubsribeTestDataSuccess,
 onFailure: doFail
 }

 // connect the client
 client.connect(options);
}
  1. 通过刷新加载了index.html的浏览器中运行代码。

  2. 点击Subscribe to test按钮。您应该会收到一个弹出对话框,显示Subscribed to test消息。

  3. 关闭弹出对话框。

  4. 点击发送测试消息按钮。

  5. 您应该在文本框中看到消息Hello from JavaScript client

这是我们刚刚执行的某种魔术吗?在某种程度上是。我们刚刚成功订阅了 MQTT Broker 上的一个主题,然后发布到相同的主题,然后在同一个 JavaScript 客户端中接收到了一条消息。要从 MQTT Broker 中观察到这一点,请执行以下操作:

  1. 登录到您的 CloudMQTT 帐户

  2. 点击 T.A.R.A.S 实例

  3. 点击 WEBSOCKET UI 菜单选项

  4. 您应该会看到以下对话框,显示您已连接:

  1. 在浏览器的另一个标签或窗口中,导航回 JavaScript 客户端index.html

  2. 再次点击发送测试消息按钮

  3. 返回 CloudMQTT 页面

  4. 在接收到的消息列表下,您应该看到一条消息:

  1. 点击发送测试消息按钮几次,您应该会在接收到的消息下看到相同消息的列表。

理解 JavaScript 代码

在为树莓派编写代码之前,让我们先看一下index.js中的 JavaScript 代码。

我们首先来看订阅代码。我们用来从我们的 MQTT Broker 订阅主题的两种方法是subscribeTestDataonsubsribeTestDataSuccesssubscribeTestData创建了一个名为client的 Paho MQTT 客户端对象。它使用client对象通过实例化对象与我们的 MQTT Broker 连接,并使用ServerWebsockets Port值(为简单起见,我在代码中留下了我的帐户中的值):

function subscribeTestData() {
    client = new Paho.MQTT.Client
        ("m10.cloudmqtt.com", 38215, "web_" +     
                        parseInt(Math.random() * 100, 10));

    // set callback handlers
    client.onConnectionLost = onConnectionLost;
    client.onMessageArrived = onMessageArrived;

    var options = {
        useSSL: true,
        userName: "vectydkb",
        password: "ZpiPufitxnnT",
        onSuccess: onsubsribeTestDataSuccess,
        onFailure: doFail
    }

    // connect the client
    client.connect(options);
}

然后,我们使用client.onConnectionLostclient.onMessageArrived设置回调处理程序。回调处理程序将我们 JavaScript 代码中的函数与我们的client对象的事件相关联。在这种情况下,当与 MQTT 代理的连接丢失或从 MQTT 代理接收到消息时。 options变量将 SSL 的使用设置为true,设置UserPassword设置,然后将成功连接的条件设置为onsubsribeTestDataSuccess方法,将连接尝试不成功的条件设置为doFail方法。然后,我们通过传递我们的options变量通过client.connect方法连接到我们的 MQTT 代理。

当成功连接到 MQTT 代理时,将调用onsubsribeTestDataSuccess方法。它设置client对象以订阅test主题。然后,它创建一个带有消息Subscribed to test的警报:

function onsubsribeTestDataSuccess() {
    client.subscribe("test");
    alert("Subscribed to test");
}

如果与客户端的连接不成功,则调用doFail方法。它只是创建一个带有消息“错误!”的弹出警报:

function doFail() {
    alert("Error!");
}

现在我们了解了订阅test主题的代码,让我们看一下发布到test主题的代码。

sendTestData函数与subscribeTestData函数非常相似:

function sendTestData() {
    client = new Paho.MQTT.Client
        ("m10.cloudmqtt.com", 38215, "web_" + parseInt(Math.random() * 100, 10));

    // set callback handlers
    client.onConnectionLost = onConnectionLost;

    var options = {
        useSSL: true,
        userName: "vectydkb",
        password: "ZpiPufitxnnT",
        onSuccess: sendTestDataMessage,
        onFailure: doFail
    }

    // connect the client
    client.connect(options);
}

创建了一个名为client的 Paho MQTT 客户端对象,其参数与subscribeTestData函数中使用的参数相同。设置的唯一回调处理程序是onConnectionLost。我们没有设置onMessageArrived,因为我们正在发送消息而不是接收消息。将options变量设置为与subscribeTestData函数中使用的相同值,唯一的例外是将onSuccess分配给sendTestDataMessage函数。

sendTestDataMessage函数创建一个新的 Paho MQTT 消息对象,其值为Hello from JavaScript client,并将其命名为messagedestinationName是我们为其创建消息的主题,设置为test值。然后,我们使用client.send发送消息:

function sendTestDataMessage() {
    message = new Paho.MQTT.Message("Hello from JavaScript client");
    message.destinationName = "test";
    client.send(message);
}

onConnectionLost函数用于订阅和发布,并简单地创建一个带有来自 JavaScript 响应对象的错误消息的警报弹出窗口:

// called when the client loses its connection
function onConnectionLost(responseObject) {
    if (responseObject.errorCode !== 0) {
        alert("onConnectionLost:" + responseObject.errorMessage);
    }
}

既然我们的 JavaScript 客户端已经订阅并发布到我们的 MQTT 代理,让我们让树莓派也参与其中。

从我们的树莓派发布 MQTT 消息

让我们返回到我们的树莓派(如果您一直在使用另一台计算机),并编写一些代码与我们的 MQTT 代理进行通信:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 打开 Thonny。

  2. 单击“新建”图标创建一个新文件。

  3. 在文件中输入以下内容:

import paho.mqtt.client as mqtt
from time import sleep

mqttc = mqtt.Client()
mqttc.username_pw_set("vectydkb", "ZpiPufitxnnT")
mqttc.connect('m10.cloudmqtt.com', 18215)

while True:
    try:
        mqttc.publish("test", "Hello from Raspberry Pi")
    except:
        print("Could not send message!")
    sleep(10)

  1. 将文件保存为CloudMQTT-example.py并运行它。

  2. 返回到 CloudMQTT 页面。您应该看到来自树莓派的消息:

  1. 导航到我们的 JavaScript 客户端index.html。您应该在文本框中看到消息Hello from the Raspberry Pi(如果您没有看到消息,请刷新页面并再次单击“Subscribe to test”):

树莓派 Python 代码故意保持简单,以便可以理解这些概念。我们通过导入所需的库来启动代码。然后,我们创建一个名为mqttc的 MQTT 客户端对象。使用username_pw_set方法设置用户名和密码。然后,我们使用connect方法连接到 MQTT 代理,通过传递ServerPort值(我们为 Python 客户端使用Port而不是Websockets Port)。在一个连续的循环内,我们通过传递主题test和消息Hello from Raspberry Pi来通过publish方法发布到 MQTT 代理。

摘要

在本章中,我们在使用 JavaScript 创建 MQTT 客户端之前探索了 JavaScript 库。我们设置了一个基于云的 MQTT 代理,并能够使用我们的 JavaScript 客户端和树莓派上的 Python 程序发布和订阅消息。

在第十八章中,将所有内容放在一起,我们将扩展本章学到的知识,并构建一个可以通过互联网控制 T.A.R.A.S 的 JavaScript 客户端。

问题

  1. 我们可以使用哪个程序(平台)在本地安装 MQTT Broker?

  2. JavaScript 和 Java 是相同的技术,是真是假?

  3. 我们可以使用 JavaScript 来创建一个 MQTT 客户端吗?

  4. 我们可以使用google-api-javascript-client库来访问哪些谷歌服务?

  5. MQTT 是物联网中使用的协议,是真是假?

  6. JavaScript Node.js 技术允许您做什么?

  7. Python 可以用于开发 MQTT 客户端,是真是假?

  8. 我们可以通过使用脚本标签将外部 JavaScript 库的功能添加到我们的网页中,是真是假?

  9. 我们如何在 JavaScript 代码中为我们的 MQTT 客户端设置用户名和密码?

  10. 我们可以在 Cloud MQTT 应用程序中查看我们发布的消息吗?

进一步阅读

有关使用基于云的 MQTT Broker 的更多信息,请参阅www.cloudmqtt.com/docs.html

第十八章:将所有内容放在一起

对于我们的最后一步,我们将让 T.A.R.A.S 响应使用 JavaScript 客户端发送的 MQTT 控制信号。我们将通过修改到目前为止编写的代码来实现这一点。如果您从头开始阅读本书,感谢您的毅力。这是一个漫长的旅程。我们终于做到了。在本章结束时,我们将完成构建物联网设备的终极目标,即一个可以通过互联网控制的机器人车。

系好安全带(双关语)-是时候将 T.A.R.A.S 提升到下一个级别了。

在本章中,我们将涵盖以下主题:

  • 构建一个 JavaScript 客户端以连接到我们的树莓派

  • JavaScript 客户端以访问我们的机器人车的感知数据

  • 增强我们的 JavaScript 客户端以控制我们的机器人车

项目概述

在本章中,我们将 T.A.R.A.S 连接到 MQTT 代理。通过 MQTT 消息,我们将控制 T.A.R.A.S 的移动,并从 T.A.R.A.S 的距离传感器中读取信息。以下是我们将要构建的图表:

我们将首先编写 HTML JavaScript 客户端(在图表中显示为HTML 客户端),并使用它发送和接收 MQTT 消息。然后,我们将把注意力转向编写 T.A.R.A.S 上的代码,以从相同的 MQTT 代理接收和发送消息。我们将使用这些消息来使用浏览器控制 T.A.R.A.S。最后,我们还将使用浏览器从 T.A.R.A.S 实时传输视频。

完成此项目应该需要半天的时间。

入门

要完成此项目,需要以下内容:

  • 一个树莓派 3 型(2015 年或更新型号)

  • 一个 USB 电源适配器

  • 一个计算机显示器

  • 一个 USB 键盘

  • 一个 USB 鼠标

  • 一个 T.A.R.A.S 机器人车

构建一个 JavaScript 客户端以连接到我们的树莓派

以下是我们将构建的 HTML JavaScript 客户端的屏幕截图,用于通过网络控制 T.A.R.A.S。HTML JavaScript 客户端可能不会赢得任何设计奖,但它将作为一个优秀的学习平台,用于通过互联网发送机器人控制信息。

大紫色按钮用于向 T.A.R.A.S 发送“前进”和“后退”命令。较小的绿色按钮向 T.A.R.A.S 发送“左转”和“右转”控制信息。底部的小银色按钮允许我们使用 T.A.R.A.S 的摄像头拍照,触发 T.A.R.A.S 的警报,并让 T.A.R.A.S 跳舞。跟踪距离按钮将 HTML JavaScript 客户端连接到 T.A.R.A.S 传来的距离信息。

在我们为树莓派构建 Python MQTT 客户端之前,我们将使用 CloudMQTT 仪表板跟踪控制信息。

编写 HTML 代码

我们将首先为我们的 HTML JavaScript 客户端编写 HTML 代码。您可以使用树莓派以外的计算机:

  1. 在您的计算机上创建一个名为HTML JavaScript Clientproject文件夹

  2. 从第十七章中复制 Paho JavaScript 库,构建 JavaScript 客户端,到project文件夹中

  3. 使用您喜欢的 HTML 编辑器,创建一个名为index.html的文件,并将其保存在步骤 1中创建的文件夹中

  4. 将以下内容输入到index.html中,然后再次保存:

<html>
    <head>
        <title>T.A.R.A.S Robot Car Control</title>
        <script src="paho.javascript-1.0.3/paho-mqtt.js" 
                        type="text/javascript"></script>        
        <script src="scripts/index.js"        
                        type='text/javascript'></script>            

        <link rel="stylesheet" href="styles/styles.css">        
    </head>
    <body>
        <h2>T.A.R.A.S Robot Car Control</h2>
        <div>
            <button onclick="moveForward()" 
                            class="big_button">    
                <h4>Forward</h4>
            </button>
        </div>
        <div>
            <button onclick="turnLeft()" 
                            class="small_button">
                <h4>Turn Left</h4>
            </button>
            <button onclick="turnRight()" 
                            class="small_button">
                <h4>Turn Right</h4>
            </button>
        </div>
        <div>
            <button onclick="moveBackward()" 
                                class="big_button">        
                <h4>Backwards</h4>
            </button>
        </div>
        <div>
            <button onclick="takePicture()" 
                            class="distance_button">        
                <h4>Take Picture</h4>
            </button>
            <button onclick="TARASAlarm()" 
                            class="distance_button">        
                <h4>T.A.R.A.S Alarm</h4>
            </button>
            <button onclick="makeTARASDance()" 
                            class="distance_button">        
                <h4>T.A.R.A.S Dance</h4>
            </button>
            <button onclick="subscribeDistanceData()" 
                            class="distance_button">
                <h4>Track Distance</h4>
            </button>
            <input type="text" id="messageTxt" value="0" 
                            size=34 class="distance" />        
        </div>
    </body>
</html>

在我们可以在浏览器中查看index.html之前,我们必须为样式创建一个.css文件。我们还将为我们的 JavaScript 文件创建一个文件夹:

  1. 在您的project文件夹中,创建一个新文件夹,并将其命名为styles

  2. project文件夹中创建另一个文件夹,并将其命名为scripts

  3. 您的project目录应该与以下内容相同:

  1. styles文件夹中,使用 HTML 编辑器创建一个名为styles.css的文件

  2. 将以下内容输入到styles.css文件中,然后保存:

.big_button {
    background-color: rgb(86, 76, 175);
    border: none;
    color: white;
    padding: 15px 32px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 16px;
    margin: 4px 2px;
    cursor: pointer;
    width: 400px;
}
.small_button {
    background-color: rgb(140, 175, 76);
    border: none;
    color: white;
    padding: 15px 32px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 16px;
    margin: 4px 2px;
    cursor: pointer;
    width: 195px;
}
.distance_button {
    background-color: rgb(192, 192, 192);
    border: none;
    color: white;
    padding: 1px 1px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 10px;
    margin: 2px 2px;
    cursor: pointer;
    width: 60px;
}
.distance {
    background-color: rgb(255, 255, 255);
    border: none;
    color: rgb(192,192,192);
    padding: 1px 1px;
    text-align: top;
    text-decoration: none;
    display: inline-block;
    font-size: 20px;
    margin: 2px 2px;
    cursor: pointer;
    width: 300px;
}
  1. 打开浏览器,导航到project文件夹中的index.html文件

  2. 您应该看到 T.A.R.A.S 机器人车控制仪表板

在添加 JavaScript 代码之前,让我们看一下我们刚刚写的内容。我们将从导入我们需要的资源开始。我们需要 Paho MQTT 库、一个index.js文件(我们还没有写),以及我们的styles.css文件。

<script  src="paho.javascript-1.0.3/paho-mqtt.js"  type="text/javascript"></script> <script  src="scripts/index.js"  type='text/javascript'></script> <link  rel="stylesheet"  href="styles/styles.css"> 

然后,我们将创建一系列按钮,将这些按钮与我们即将编写的index.js JavaScript 文件中的函数绑定:

<div>
 <button  onclick="moveForward()"  class="big_button"> <h4>Forward</h4> </button> </div>

由于我们的按钮几乎相似,我们只讨论第一个按钮。第一个按钮通过onclick属性绑定到我们 JavaScript 文件中的moveForward函数。按钮的样式通过将class分配给big_button来设置。我们使用第一个按钮来向前移动 T.A.R.A.S。

编写与我们的 MQTT 代理通信的 JavaScript 代码

现在我们有了 HTML 和 CSS 文件,让我们创建一个 JavaScript 文件,让 MQTT 的魔力发生:

  1. scripts文件夹中,使用 HTML 编辑器创建一个名为index.js的文件。

  2. index.js文件中输入以下内容并保存:

function moveForward() {
    client = new Paho.MQTT.Client("m10.cloudmqtt.com", 38215, "web_" + parseInt(Math.random() * 100, 10));

    // set callback handlers
    client.onConnectionLost = onConnectionLost;
    var options = {
        useSSL: true,
        userName: "vectydkb",
        password: "ZpiPufitxnnT",
        onSuccess: sendMoveForwardMessage,
        onFailure: doFail
    }

    // connect the client
    client.connect(options);
}

// called when the client connects
function sendMoveForwardMessage() {
    message = new Paho.MQTT.Message("Forward");
    message.destinationName = "RobotControl";
    client.send(message);
}

function moveBackward() {
    client = new Paho.MQTT.Client("m10.cloudmqtt.com", 38215, "web_" + parseInt(Math.random() * 100, 10));

    // set callback handlers
    client.onConnectionLost = onConnectionLost;
    var options = {
        useSSL: true,
        userName: "vectydkb",
        password: "ZpiPufitxnnT",
        onSuccess: sendMoveBackwardMessage,
        onFailure: doFail
    }

    // connect the client
    client.connect(options);
}

// called when the client connects
function sendMoveBackwardMessage() {
    message = new Paho.MQTT.Message("Backward");
    message.destinationName = "RobotControl";
    client.send(message);
}

function turnLeft() {
    client = new Paho.MQTT.Client("m10.cloudmqtt.com", 38215, "web_" + parseInt(Math.random() * 100, 10));

    // set callback handlers
    client.onConnectionLost = onConnectionLost;
    var options = {
        useSSL: true,
        userName: "vectydkb",
        password: "ZpiPufitxnnT",
        onSuccess: sendTurnLeftMessage,
        onFailure: doFail
    }

    // connect the client
    client.connect(options);
}

// called when the client connects
function sendTurnLeftMessage() {
    message = new Paho.MQTT.Message("Left");
    message.destinationName = "RobotControl";
    client.send(message);
}

function turnRight() {
    client = new Paho.MQTT.Client("m10.cloudmqtt.com", 38215, "web_" + parseInt(Math.random() * 100, 10));

    // set callback handlers
    client.onConnectionLost = onConnectionLost;
    var options = {
        useSSL: true,
        userName: "vectydkb",
        password: "ZpiPufitxnnT",
        onSuccess: sendTurnRightMessage,
        onFailure: doFail
    }

    // connect the client
    client.connect(options);
}

// called when the client connects
function sendTurnRightMessage() {
    message = new Paho.MQTT.Message("Right");
    message.destinationName = "RobotControl";
    client.send(message);
}

function takePicture() {
    client = new Paho.MQTT.Client("m10.cloudmqtt.com", 38215, "web_" + parseInt(Math.random() * 100, 10));

    // set callback handlers
    client.onConnectionLost = onConnectionLost;
    var options = {
        useSSL: true,
        userName: "vectydkb",
        password: "ZpiPufitxnnT",
        onSuccess: sendTakePictureMessage,
        onFailure: doFail
    }

    // connect the client
    client.connect(options);
}

// called when the client connects
function sendTakePictureMessage() {
    message = new Paho.MQTT.Message("Picture");
    message.destinationName = "RobotControl";
    client.send(message);
}

function TARASAlarm() {
    client = new Paho.MQTT.Client("m10.cloudmqtt.com", 38215, "web_" + parseInt(Math.random() * 100, 10));

    // set callback handlers
    client.onConnectionLost = onConnectionLost;
    var options = {
        useSSL: true,
        userName: "vectydkb",
        password: "ZpiPufitxnnT",
        onSuccess: sendTARASAlarmMessage,
        onFailure: doFail
    }

    // connect the client
    client.connect(options);
}

// called when the client connects
function sendTARASAlarmMessage() {
    message = new Paho.MQTT.Message("Alarm");
    message.destinationName = "RobotControl";
    client.send(message);
}

function makeTARASDance() {
    client = new Paho.MQTT.Client("m10.cloudmqtt.com", 38215, "web_" + parseInt(Math.random() * 100, 10));

    // set callback handlers
    client.onConnectionLost = onConnectionLost;
    var options = {
        useSSL: true,
        userName: "vectydkb",
        password: "ZpiPufitxnnT",
        onSuccess: makeTARASDanceMessage,
        onFailure: doFail
    }

    // connect the client
    client.connect(options);
}

// called when the client connects
function makeTARASDanceMessage() {
    message = new Paho.MQTT.Message("Dance");
    message.destinationName = "RobotControl";
    client.send(message);
}

function doFail() {
    alert("Error!");
}

// called when the client loses its connection
function onConnectionLost(responseObject) {
    if (responseObject.errorCode !== 0) {
        alert("onConnectionLost:" + responseObject.errorMessage);
    }
}

// called when a message arrives
function onMessageArrived(message) {
    document.getElementById('messageTxt').value = message.payloadString; 
}

function onsubsribeDistanceDataSuccess() {
    client.subscribe("distance");
    alert("Subscribed to distance data");
}

function subscribeDistanceData() {
    client = new Paho.MQTT.Client("m10.cloudmqtt.com", 38215, "web_" + parseInt(Math.random() * 100, 10));

    // set callback handlers
    client.onConnectionLost = onConnectionLost;
    client.onMessageArrived = onMessageArrived;
    var options = {
        useSSL: true,
        userName: "vectydkb",
        password: "ZpiPufitxnnT",
        onSuccess: onsubsribeDistanceDataSuccess,
        onFailure: doFail
    }

    // connect the client
    client.connect(options);
}
  1. 我已经在代码中留下了我的 CloudMQTT 实例的值。就像我们在第十七章中所做的那样,构建 JavaScript 客户端,用您实例的值(服务器Websockets 端口用户名密码)替换这些值。

  2. 在浏览器中导航回到index.html并刷新页面。

  3. 现在我们已经有了我们的 HTML JavaScript 客户端。我们所做的实质上是修改了第十七章中的index.js代码,构建 JavaScript 客户端,以便我们可以向我们的 MQTT 代理发送控制消息,最终控制我们的机器人车:

function moveForward() {
    client = new Paho.MQTT.Client("m10.cloudmqtt.com", 38215, "web_" + parseInt(Math.random() * 100, 10));

    // set callback handlers
    client.onConnectionLost = onConnectionLost;
    var options = {
        useSSL: true,
        userName: "vectydkb",
        password: "ZpiPufitxnnT",
        onSuccess: sendMoveForwardMessage,
        onFailure: doFail
    }

    // connect the client
    client.connect(options);
}

// called when the client connects
function sendMoveForwardMessage() {
    message = new Paho.MQTT.Message("Forward");
    message.destinationName = "RobotControl";
    client.send(message);
}

我们已经更改了上一个示例中的代码。moveForward函数创建了一个名为client的 Paho MQTT 客户端,其中包含从我们的 CloudMQTT 实例获取的服务器Websockets 端口连接信息。设置了一个回调处理程序来处理连接丢失时的情况,该处理程序设置为onConnectionLost函数。使用从我们的 CloudMQTT 实例获取的userNamepassword信息创建了options变量。我们将成功连接到 MQTT 代理设置为sendMoveForwardMessage函数。然后通过传入options变量连接到我们的客户端。

sendMoveForwardMessage函数创建了一个名为Forward的新 Paho MQTT 消息。然后将此消息分配给RobotControl主题,并使用我们的 Paho MQTT 客户端对象client发送。

发送后退、右转、左转、拍照、触发警报和跳舞的消息的函数以类似的方式编写为moveForward函数。

现在我们已经为控制 T.A.R.A.S 在网络上构建了 HTML JavaScript 客户端,让我们使用 CloudMQTT 实例上的WEBSOCKETS UI页面进行测试:

  1. 导航回到您的 CloudMQTT 帐户。

  2. 选择您获取服务器、用户、密码和 Web 套接字端口连接信息的实例(在第十七章中,构建 JavaScript 客户端,我们创建了名为T.A.R.A.S的实例)。

  3. 点击左侧的 WEBSOCKETS UI 菜单选项。您应该在右侧收到一个成功连接的通知。

  4. 导航回到index.html并点击“前进”按钮。

  5. 现在,导航回到您的 CloudMQTT 实例。您应该在“接收到的消息”列表中看到一条新消息:

恭喜!您刚刚连接了一个 HTML JavaScript 客户端到一个 MQTT 代理并发送了一条消息。现在我们将在另一台设备上使用完全不同的编程语言开发另一个客户端,然后使用该客户端订阅来自我们的 HTML JavaScript 客户端的消息。

创建一个 JavaScript 客户端来访问我们机器人车的感知数据

我们创建的index.js文件包含订阅我们的 HTML JavaScript 客户端到distance主题的函数:

function subscribeDistanceData() {
    client = new Paho.MQTT.Client("m10.cloudmqtt.com", 38215, "web_" + parseInt(Math.random() * 100, 10));

    // set callback handlers
    client.onConnectionLost = onConnectionLost;
    client.onMessageArrived = onMessageArrived;
    var options = {
        useSSL: true,
        userName: "vectydkb",
        password: "ZpiPufitxnnT",
        onSuccess: onsubsribeDistanceDataSuccess,
        onFailure: doFail
    }

    // connect the client
    client.connect(options);
}

function onsubsribeDistanceDataSuccess() {
    client.subscribe("distance");
    alert("Subscribed to distance data");
}

类似于我们在第十七章中编写的代码,构建 JavaScript 客户端subscribeDistanceData函数创建了一个 Paho MQTT 客户端,其中包含来自 CloudMQTT 实例的连接信息。成功连接后,将调用onsubscribeDistanceDataSuccess函数,该函数将client订阅到distance主题。

还创建了一个警报,告诉我们 HTML JavaScript 客户端现在已订阅了distance主题。

编写 T.A.R.A.S 的代码

现在我们将把注意力转回到我们的树莓派机器人车上,并编写 Python 代码来与我们的 MQTT 代理通信,最终与我们的 HTML JavaScript 客户端通信。以下代码应直接从 T.A.R.A.S 运行。如果您想要无线运行 T.A.R.A.S,请使用 USB 电源适配器为树莓派供电,并在运行以下程序后断开 HDMI 电缆:

  1. 从应用程序菜单 | 编程 | Thonny Python IDE 中打开 Thonny。

  2. 单击新图标创建一个新文件。

  3. 将以下代码输入文件中:

import paho.mqtt.client as mqtt
from time import sleep
from RobotDance import RobotDance
from RobotWheels import RobotWheels
from RobotBeep import RobotBeep
from RobotCamera import RobotCamera
from gpiozero import DistanceSensor

distance_sensor = DistanceSensor(echo=18, trigger=17)

def on_message(client, userdata, message):
    command = message.payload.decode("utf-8")

    if command == "Forward":
        move_forward()
    elif command == "Backward":
        move_backward()
    elif command == "Left":
        turn_left()
    elif command == "Right":
        turn_right()
    elif command == "Picture":
        take_picture()
    elif command == "Alarm":
        sound_alarm()
    elif command == "Dance":
        robot_dance()

def move_forward():
    robotWheels = RobotWheels()
    robotWheels.move_forward()
    sleep(1)
    print("Moved forward")
    robotWheels.stop()
    watchMode()

def move_backward():
    robotWheels = RobotWheels()
    robotWheels.move_backwards()
    sleep(1)
    print("Moved backwards")
    robotWheels.stop()
    watchMode()

def turn_left():
    robotWheels = RobotWheels()
    robotWheels.turn_left()
    sleep(1)
    print("Turned left")
    robotWheels.stop()
    watchMode()

def turn_right():
    robotWheels = RobotWheels()
    robotWheels.turn_right()
    print("Turned right")
    robotWheels.stop()
    watchMode()

def take_picture():
    robotCamera = RobotCamera()
    robotCamera.take_picture()
    watchMode()

def sound_alarm():
    robotBeep = RobotBeep()
    robotBeep.play_song()

def robot_dance():
    robotDance = RobotDance()
    robotDance.lets_dance_incognito()
    print("Finished dancing now back to work")
    watchMode()

def watchMode():
    print("Watching.....")
    mqttc = mqtt.Client()
    mqttc.username_pw_set("vectydkb", "ZpiPufitxnnT")
    mqttc.connect('m10.cloudmqtt.com', 18215)
    mqttc.on_message = on_message
    mqttc.subscribe("RobotControl")

    while True:
        distance = distance_sensor.distance*100
        mqttc.loop()
        mqttc.publish("distance", distance)
        sleep(2)

watchMode()
  1. 将文件保存为MQTT-RobotControl.py

  2. 从 Thonny 运行代码。

  3. 转到 HTML JavaScript 客户端,然后单击前进按钮:

  1. T.A.R.A.S 应该向前移动一秒,然后停止。

  2. 底部的小灰色按钮允许您执行与 T.A.R.A.S 的各种任务:

  1. 通过单击这些按钮来探索每个按钮的功能。Take Picture按钮将拍照并将其存储在文件系统中,T.A.R.A.S Alarm将在 T.A.R.A.S 上触发警报,T.A.R.A.S Dance将使 T.A.R.A.S 跳舞。

  2. 要订阅来自 T.A.R.A.S 距离传感器的distance数据,请单击 Track Distance 按钮:

  1. 单击 Track Distance 按钮后,您应该会看到一个弹出窗口,告诉您 HTML JavaScript 客户端现在已订阅了distance数据:

  1. 单击关闭以关闭弹出窗口。现在您应该看到 T.A.R.A.S 的距离数据信息显示在 Track Distance 按钮旁边。

  2. 与迄今为止我们编写的所有代码一样,我们的目标是使其尽可能简单和易于理解。我们代码的核心是watch_mode方法:

def watchMode():
    print("Watching.....")
    mqttc = mqtt.Client()
    mqttc.username_pw_set("vectydkb", "ZpiPufitxnnT")
    mqttc.connect('m10.cloudmqtt.com', 18215)
    mqttc.on_message = on_message
    mqttc.subscribe("RobotControl")

    while True:
        distance = distance_sensor.distance*100
        mqttc.loop()
        mqttc.publish("distance", distance)
        sleep(2)

watch_mode方法是我们代码的默认方法。它在代码运行后立即调用,并在另一个方法完成时调用。在watch_mode中,我们需要创建一个名为mqttc的 MQTT 客户端对象,然后使用它连接到我们的 CloudMQTT 实例。从那里,我们将on_message回调设置为on_message方法。然后我们订阅RobotControl主题。随后的 while 循环调用我们的 MQTT 客户端mqttcloop方法。由于我们已经设置了on_message回调,因此每当从RobotControl主题接收到消息时,程序都会退出 while 循环,并执行我们代码的on_message方法。

watch_mode中,每 2 秒将距离传感器信息发布到distance主题。由于我们的 HTML JavaScript 客户端已设置为订阅distance主题上的消息,因此我们的 HTML JavaScript 客户端将每两秒在页面上更新distance信息。

从 T.A.R.A.S 直播视频。

从网络上控制 T.A.R.A.S 是一件了不起的事情,但如果我们看不到我们在做什么,那就没什么用了。如果你在树莓派上安装 RPi-Cam-Web-Interface,就可以很简单地从树莓派上直播视频。现在让我们来做这个:

  1. 如果您的树莓派上没有安装git,请在终端中使用sudo apt-get install git进行安装。

  2. 使用终端,通过运行git clone https://github.com/silvanmelchior/RPi_Cam_Web_Interface.git命令获取安装文件。

  3. 使用cd RPi_Cam_Web_Interface命令更改目录。

  4. 使用./install.sh命令运行安装程序。

  5. 您应该看到配置选项屏幕:

  1. 通过在键盘上按Tab,接受所有默认设置,直到 OK 选项被突出显示。然后按Enter

  2. 在看到“现在启动摄像头系统”对话框时选择“是”:

  1. 现在,我们已经准备好从我们的树莓派(T.A.R.A.S)实时传输视频。在另一台计算机上,打开浏览器,输入地址http://<<您的树莓派 IP 地址>>/html(在您的树莓派上使用ifconfig来查找您的 IP 地址;在我的情况下,视频流的 URL 是http://192.168.0.31/html)。

  2. 现在,您应该看到视频流播放器加载到您的浏览器中,并从您的树莓派实时播放视频。以下是我办公室 T.A.R.A.S 的直播截图,显示我的无人机:

RPi-Cam-Web-Interface 实用程序是一个令人惊叹的工具。花些时间尝试一下可用的各种选项和功能。

增强我们的 JavaScript 客户端以控制我们的机器人小车

正如我们已经提到的,我们的 HTML JavaScript 客户端是最具吸引力的界面。我设计它尽可能简单直接,以便解释各种概念。但是,如果我们想把它提升到另一个水平呢?以下是一些可能用于增强我们的 HTML JavaScript 客户端的 JavaScript 库的列表。

Nipple.js

Nipple.js (www.bypeople.com/touch-screen-joystick/)是一个 JavaScript 触摸屏操纵杆库,可用于控制机器人。Nipple.js 基本上是一种屏幕上的指向杆控制,类似于一些笔记本电脑上的控制。

如果您要为触摸屏平板电脑或笔记本电脑创建 JavaScript 客户端,Nipple.js 可能是一个很好的构建技术。将 Nipple.js 等技术纳入我们的设计中,需要相当多的编码工作,以便将移动转换为 T.A.R.A.S 能理解的消息。简单的前进消息可能不够。消息可能是Forward-1-Left-2.3之类的,必须对其进行解析并提取信息,以确定转动电机的时间和移动哪些电机。

HTML5 Gamepad API

您想连接物理操纵杆来控制我们的机器人小车吗?您可以使用 HTML5 Gamepad API (www.w3.org/TR/gamepad/)。使用 HTML5 Gamepad API,您可以在构建的 Web 应用程序中使用标准游戏操纵杆。通过 HTML5 Gamepad API 控制您的机器人小车可能就像玩您最喜欢的视频游戏一样简单。

Johnny-Five

Johnny-Five (johnny-five.io)是一个 JavaScript 机器人和物联网平台。这是一个完全不同于我们为机器人小车开发的平台。现在我们已经从头开始构建了我们的机器人小车,并且已经手工编写了控制代码,我们可能有兴趣尝试一些新东西。Johnny-Five 可能是您决定成为专家的下一个技术。

摘要

我们做到了!我们已经完成了树莓派物联网之旅。在本章中,我们将所学知识整合在一起,并创建了自己的 HTML JavaScript 客户端,用于通过网页控制 T.A.R.A.S。我们使用类来控制 T.A.R.A.S,使得创建控制代码相对容易,因为我们只需要在类上调用方法,而不是从头开始创建控制代码。

我们简要介绍了如何轻松地从树莓派实时传输视频。尽管我们做所有这些是为了通过网络控制机器人小车,但不难想象我们可以利用所学知识来构建任意数量的不同物联网项目。

我们生活在一个非常激动人心的时代。我们中的任何一个人都可以仅凭我们的智慧和一些相对便宜的电子元件来构建下一个杀手级应用程序。如果可能的话,我希望我能激励您使用令人惊叹的树莓派计算机来构建您的下一个伟大项目。

对于那些质疑我们如何将这视为物联网项目的人,当我们只使用我们的本地网络时,请研究一下如何在路由器上打开端口以连接外部世界。然而,这不是一项应该轻率对待的任务,因为在这样做时必须解决安全问题。请注意,您的互联网服务提供商可能没有为您提供静态 IP 地址,因此您构建的任何用于从外部访问您的网络的东西都会在 IP 地址更改时中断(我曾经构建过一个定期检查我的 IP 地址的 PHP 页面,存储最新地址,并有外部客户端会访问该 PHP 获取地址,而不是将其硬编码)。

问题

  1. 在我们的项目中,我们向哪个主题发布控制类型的消息?

  2. 真或假?MQTT Broker 和 MQTT Server 是用来描述同一件事情的词语。

  3. 真或假?T.A.R.A.S 在相同的 MQTT 主题上发布和订阅。

  4. 我们的 HTML JavaScript 客户端中的大前进和后退按钮是什么颜色?

  5. 真或假?使用 HTML JavaScript 客户端,我们能够远程使用 T.A.R.A.S 上的摄像头拍照。

  6. 我们使用什么 MQTT 主题名称来订阅来自 T.A.R.A.S 的距离数据?

  7. 真或假?我们的 HTML JavaScript 客户端采用了屡获殊荣的 UI 设计。

  8. 真或假?使用我们的 CloudMQTT 账户,我们能够查看我们实例中发布的消息。

  9. 我们使用什么技术来从 T.A.R.A.S 进行视频直播?

  10. 真或假?Johnny-Five 是可口可乐公司推出的一种新果汁饮料。

进一步阅读

当我们在 T.A.R.A.S 上设置实时流时,我们简要地介绍了 RPi-Cam-Web-Interface 网页界面。这个网页界面非常惊人,对它的更深入了解只会增强我们对树莓派的所有可能性的理解。请访问elinux.org/RPi-Cam-Web-Interface获取更多信息。

第十九章:评估

第一章

  1. 第一款树莓派是在哪一年推出的?

A. 2012

  1. 树莓派 3 Model B+相对于上一个版本有哪些升级?

A. 处理器升级到 1.4 GHz,支持 5 GHz 无线局域网,蓝牙低功耗。

  1. NOOBS 代表什么?

A. 新开箱即用软件

  1. 允许使用 Python 代码创建音乐的预安装应用程序的名称是什么?

A. Sonic Pi

  1. 树莓派的操作系统存储在哪里?

A. 在 microSD 卡上

  1. 为儿童设计的可视化编程环境的名称是什么,它们预装在 Raspbian 中?

A. Scratch 和 Scratch 2

  1. Mathematica 中使用的语言名称是什么?

A. Wolfram

  1. Raspbian 的默认用户名和密码是什么?

A. pi / raspberry

  1. GPIO 代表什么?

A. 通用输入输出

  1. RetroPie 是什么?

A. 复古游戏模拟器

  1. 真/假?单击主栏上的两个文件夹图标会加载home文件夹。

A. True

  1. 真/假?microSD 卡槽位于树莓派底部。

A. True

  1. 真/假?要关闭树莓派,从“应用程序”菜单中选择“关闭”。

A. True

  1. 真/假?只能使用 NOOBS 安装 Raspbian 操作系统。

A. False

  1. 真/假?低功耗蓝牙是指吃太多蓝莓并且早上很难醒来的人。

A. False

第二章

  1. Thonny 适用于哪些操作系统?

A. Linux(Raspbian),macOS 和 Windows

  1. 我们如何从终端命令行进入 Python 2?

A. 通过输入命令python.

  1. Thonny 中的哪个工具用于查看对象内部的内容?

A. 对象检查器

  1. 在我们的天气示例代码中,为什么要使用对象?

A. 使代码清晰并为以后使用类做准备。

  1. CurrentWeather类添加名为getCity的方法的优点是什么?

A. 我们能够使用更通用的名称创建类。

  1. IDLE 是用什么语言编写的?

A. Python

  1. 为了打印当前日期和时间,需要采取哪两个步骤?

A. 从datetime导入datetime,打印datetime.now()

  1. 我们如何在代码中补偿只用一个字母表示的风速方向?

A. 通过使用if语句设置wind_dir_str_len

  1. if __name__ =="__main__"语句是做什么的?

A. 允许测试类。

  1. IDLE 代表什么?

A. 集成开发和学习环境

第三章

  1. 允许您访问树莓派摄像头模块的 Python 软件包的名称是什么?

A. picamera

  1. 真/假?由学生编写的代码的树莓派被部署到国际空间站上。

A. True

  1. Sense HAT 包括哪些传感器?

A. 加速计、温度传感器、磁力计、气压传感器、湿度传感器、陀螺仪。

  1. 真/假?我们不需要购买树莓派 Sense HAT 进行开发,因为 Raspbian 中存在该 HAT 的模拟器。

A. True

  1. GPIO 上有多少个接地引脚?

A. 8

  1. 真/假?树莓派的 GPIO 引脚提供 5V 和 3.3V。

A. True

  1. Pibrella 是什么?

A. Pibrella 是一个相对便宜的树莓派 HAT,可使连接到 GPIO 变得容易。

  1. 真/假?只能在早期的树莓派计算机上使用 Pibrella。

A. False

  1. BCM 模式是什么意思?

A. 用于通过 GPIO 编号访问 GPIO 引脚。

  1. 真/假?BOARD 是 BCM 的替代品。

A. True

  1. gpiozero中的 Zero 指的是什么?

A. 零样板或设置代码。

  1. 真/假?使用 Fritzing,我们可以为树莓派设计 GPIO 电路。

A. True

  1. gpiozero LED blink函数中的默认背景参数设置为什么?

A. False

  1. 真/假?使用gpiozero库访问 GPIO 比使用RPi.GPIO库更容易。

A. True

  1. 维多利亚时代的互联网是什么?

A. 19 世纪的电报和跨世界电报电缆。

第四章

  1. IBM Watson 是什么?

A. IBM Watson 是一个能够用自然语言回答问题的系统。

  1. 正确/错误? 亚马逊的物联网网络服务允许访问亚马逊的其他基于云的服务。

A. 真

  1. 正确/错误?Watson 是游戏节目 Jeopardy 的冠军。

A. 真

  1. 正确/错误?谷歌拥有自己的全球私人网络。

A. 真

  1. 正确/错误?当我们引入网络服务数据时,我们需要更改我们的函数名称,比如getTemperature

A. 假

  1. 正确/错误?在您的类中使用测试代码以隔离其功能是个好主意。

A. 真

  1. DisplayWeather类在我们的代码中的目的是什么?

A. 在 Sense HAT 模拟器中显示天气信息。

  1. 我们使用SenseHat对象的哪个方法将天气信息显示到 Sense HAT 模拟器上?

A. show_message

第五章

  1. 正确/错误?步进电机是使用开环反馈系统控制的。

A. 真

  1. 如果您正在建造电动汽车,您会使用哪种类型的电动机?

A. 直流电机

  1. 正确/错误?伺服电机被认为是步进电机的高性能替代品。

A. 真

  1. 控制伺服电机的角度是什么?

A. 伺服电机的角度由传递到伺服控制引脚的脉冲决定。

  1. 正确/错误?直流电机的响应时间比步进电机短。

A. 真

  1. 我们使用哪个 Python 包来控制我们的伺服?

A. gpiozero

  1. 正确/错误?我们能够使用 Thonny 中的 Python shell 来控制伺服。

A. 真

  1. 用于将伺服移动到其最大位置的命令是什么?

A. servo.max()

  1. 正确/错误?我们只能将伺服移动到其最小、最大和中性位置。

A. 假

  1. 我们如何将百分比值转换为我们的代码中的servo对象理解的相应值?

A. 我们将百分比值乘以 0.02,然后减去 1。

第六章

  1. 正确/错误?伺服可以用作物联网设备。

A. 真

  1. 正确/错误?更改Servo对象上的最小和最大脉冲宽度值会修改伺服的范围。

A. 真

  1. 为什么我们在调用Servo对象的close()方法之前添加延迟?

A. 为了延迟关闭伺服,以便在设置到位置之前不会关闭。

  1. 正确/错误?我们在我们的WeatherData类中不需要getTemperature()方法。

A. 真

  1. 正确/错误?我们仪表板上闪烁的 LED 表示天气晴朗。

A. 假

  1. 我们在我们的仪表板上使用一对短裤来表示什么?

A. 夏季天气

  1. 我们在代码中的哪里使用正则表达式?

A. 在getLEDValue方法中。

  1. 为什么我们在代码中导入time

为了延迟关闭到伺服的连接

  1. 正确/错误?物联网使能的伺服只能用于指示天气数据。

A. 假

第七章

  1. 正确/错误?这是 CherryPi 而不是 CherryPy。

A. 假

  1. 正确/错误?Netflix 使用 CherryPy。

A. 真

  1. 我们如何告诉 CherryPy 我们要公开一个方法?

通过使用@cherrypy.expose装饰器

  1. 正确/错误?CherryPy 需要许多样板代码。

A. 假

  1. 正确/错误?CherryPy 使用的默认端口是8888

A. 假

  1. 为什么我们在我们的col CSS 类中添加边距?

为了使圆角框不互相接触

  1. 我们使用哪个 Bootstrap 组件作为我们的内容容器?

A. 卡片

  1. 正确/错误?在我们的例子中,伦敦是晴天和炎热的。

A. 假

第八章

  1. 有源蜂鸣器和无源蜂鸣器之间有什么区别?

A. 有源蜂鸣器具有内部振荡器,当直流电流(或 DC)施加在上面时会发出声音。无源蜂鸣器需要交流电流(或 AC)才能发出声音。

  1. 正确/错误?我们检查button.is_pressed参数以确认我们的按钮是否被按下。

A. 真

  1. 正确/错误?我们需要一个电压分压电路才能连接我们的 PIR 传感器。

A. 假

  1. 我们可以使用哪三种不同的方法让我们的有源蜂鸣器发出哔哔声?

buzzer.on()buzzer.off()之间有一个延迟,buzzer.toggle()buzzer.beep()

  1. 真/假?按钮必须直接连接到电路才能发挥作用。

A. 假

  1. 我们使用哪个DistanceSensor参数来检查物体与距离传感器的距离?

A. 距离参数

  1. 我们从 Sense HAT 模拟器中使用哪个方法将像素打印到屏幕上?

A. set_pixels方法

  1. 我们如何设置我们的MotionSensor来从 GPIO 引脚 4 读取?

A. 将正极引脚连接到 5 伏特,负极引脚连接到 GND,信号引脚连接到 GPIO 4

  1. 真/假?基本的报警系统对于我们的树莓派来说太复杂了。

A. 假

  1. 真/假?Sense HAT 模拟器可以用于与连接到 GPIO 的外部传感器进行交互。

A. 真

第九章

  1. 真/假?DHT11 传感器是一种价格昂贵且高精度的温湿度传感器?

A. 假

  1. 真/假?DHT11 传感器能够检测到来自太阳的紫外线?

A. 假

  1. 真/假?在 Raspbian 中预先安装了运行 DHT11 所需的代码?

A. 假

  1. 如何设置 Pi 摄像头模块的分辨率?

A. 通过PiCameraresolution属性。

  1. 如何设置 CherryPy 以便可以访问本地静态文件?

A. 通过配置。

  1. 如何为网页设置自动刷新?

A. <meta http-equiv="refresh" content="30">

  1. 真/假?通过使用 CSS,我们能够模拟闪烁的 LED?

A. 真

  1. SecurityData类的目的是什么?

A. 提供仪表板的数据。

  1. 我们找到了谁或什么作为我们的入侵者?

A. 一只狗。

  1. 如果我们想要一丝不苟,我们如何改变我们的SecurityData类?

A. 我们将使用开关和 PIR 传感器的值初始化SecurityData类。

第十章

  1. 我们用来从树莓派发送短信的服务的名称是什么?

A. Twilio

  1. 真/假?我们使用 PIR 传感器来读取温度和湿度值?

A. 假

  1. 如何在 ThingsBoard 中创建仪表板?

A. 你可以从设备的遥测数据创建一个仪表板

  1. 真/假?我们通过使用感官仪表板来构建我们的增强安全仪表板?

A. 真

  1. 我们用来读取温度和湿度传感器数据的库的名称是什么?

A. Adafruit_DHT

  1. 真/假?我们需要发送短信的库在 Raspbian 中预先安装了吗?

A. 假

  1. 在我们的代码中命名类时,我们试图做什么?

A. 根据它们代表的内容命名它们

  1. 真/假?为了将我们的环境从测试更改为增强的家庭安全仪表板,我们必须重新编写整个代码?

A. 假

  1. 真/假?我们的 Twilio 帐户的account_sid号码在测试环境和实际环境中是相同的。

A. 真

  1. 我们在哪里在我们的SecurityDashboardDist.py代码中创建了一个SecurityDashboardDist对象?

A. 在if __name__=="__main__":部分下

第十一章

  1. RGB LED 与普通 LED 有何不同?

A. RGB LED 基本上是一个单元中的三个 LED(红色、绿色、蓝色)。

  1. 真/假?蓝点应用程序可以在 Google Play 商店中找到。

A. 真

  1. 共阳极是什么?

A. 一些 RGB LED 具有共阳极的公共正极(+),因此被称为具有共阳极

  1. 真/假?RGB LED 内的三种颜色是红色、绿色和黄色。

A. 假

  1. 如何将蓝点应用程序与树莓派配对?

A. 通过从蓝牙下拉菜单中使用 Make Discoverable

  1. 真/假?蓝牙是一种用于极远距离的通信技术。

A. 假

  1. DoorbellAlarmDoorbellAlarmAdvanced之间有什么区别?

类属性延迟用于更改蜂鸣器响铃之间的延迟时间。

  1. 真/假?GPIO Zero 库包含一个名为RGBLED的类。

A. 真

  1. 真/假?蓝点应用程序可用于记录滑动手势。

A. 真

  1. SimpleDoorbellSecretDoorbell类之间有什么区别?

A. SecretDoorbell利用蓝点应用程序中的滑动手势。

第十二章

  1. 蓝点应用程序如何连接到我们的树莓派?

A. 通过蓝牙。

  1. 真/假? 通过 Twilio 测试环境运行消息会创建发送到您手机的短信。

A. 假

  1. 我们用来发送短信的服务的名称是什么?

A. Twilio

  1. 真/假? 我们将我们的SecretDoorbell类创建为Doorbell类的子类。

A. 真

  1. 我们在第二个应用程序中使用的四个蓝点手势是什么?

A. swipe.upswipe.downswipe.leftswipe.right

  1. 真/假? 为类命名使编码更容易。

A. 真

  1. DoorbellSecretDoorbell之间有什么区别?

A. SecretDoorbell允许使用秘密手势,以便我们知道谁在门口。

  1. 真/假? Josephine 的铃声模式包括一个长的蜂鸣器声音。

A. 真

  1. 真/假? 您需要使用 Android 手机才能从我们的应用程序接收短信。

A. 假

  1. 康斯坦斯应该如何滑动蓝点,以便我们知道她在门口?

A. 康斯坦斯应该向右滑动蓝点。

第十三章

  1. 真/假? T.A.R.A.S 代表技术先进的机器人更优越?

A. 假

  1. 主动蜂鸣器和被动蜂鸣器之间有什么区别?

A. 主动蜂鸣器在施加直流电压时发出声音。被动蜂鸣器需要交流电压。被动蜂鸣器需要更多的编码。被动蜂鸣器更像小音箱,因此您可以控制从中发出的声音。

  1. 真/假? T.A.R.A.S 有摄像头作为眼睛?

A. 假

  1. 电机驱动板的作用是什么?

A. 控制电机

  1. Adafruit 伺服 HAT 的目的是什么?

A. 用于驱动摄像头支架的舵机。

  1. 打印一个轮子支架需要多长时间?

A. 30 分钟

  1. 机器人脸的目的是什么?

A. 机器人上的面孔用作人类的视觉线索。

  1. 真/假? 钩带是固定电池到底盘上的好方法。

A. 真

第十四章

  1. 真/假? LEDBoard对象允许我们同时控制许多 LED。

A. 真

  1. 真/假? RobotCamera对象上的notes列表用于移动摄像头支架。

A. 假

  1. 真/假? 我们虚构故事中的对手喜欢跳舞。

A. 真

  1. dancesecret_dance方法之间有什么区别?

A. secret_dance拍照

  1. 机器人的gpiozero库的名称是什么?

A. 机器人

  1. 揭露犯罪的警笛启发术语是什么?

A. 吹哨人

  1. 真/假? 封装控制代码是一个毫无意义和不必要的步骤。

A. 假

  1. TailLights类的目的是什么?

A. 封装 LED 闪烁模式

  1. 我们会使用哪个类和方法来使机器人车向右转?

A. 机器人类和right()方法

  1. RobotCamera类的目的是什么?

A. 封装头部运动和摄像头功能

第十五章

  1. 连接 HC-SR04 到树莓派时,为什么我们使用电压分压电路?

A. 5 伏特对于我们的树莓派来说是太高的电压

  1. 真/假? T.A.R.A.S 有通过声纳看到的眼睛?

A. 真

  1. 在 ThingsBoard 中,设备是什么?

它是用于在 ThingsBoard 中发布 MQTT 数据的组件

  1. 真/假? 我们的RobotEyes类封装了 T.A.R.A.S 上使用的树莓派摄像头模块?

A. 假

  1. 方法RobotEyes.publish_distance的作用是什么?

A. 这些方法将距离传感器数据发送到 ThingsBoard 仪表板。

  1. 真/假? 我们需要使用 MQTT 的库与 Raspbian 预装?

A. 假

  1. 为什么我们将我们的类命名为RobotEyes而不是RobotDistanceSensor

A. 我们不需要知道眼睛是由距离传感器组成的。这使我们能够更改类的内部工作方式,而无需更改类与之交互的代码。

  1. 真/假?将样板代码封装在一个类中会使代码更难处理?

A. 假

  1. 真/假?GPIO Zero 库不支持距离传感器。

A. 假

  1. RobotEyes.pyRobotEyesIOT.py之间有什么区别?

A. RobotEyesIOT将感官信息发布到互联网,而RobotEyes则不会。

第十六章

  1. 一个无人驾驶汽车需要从中央站获得什么类型的信息?

A. 交通和道路状况

  1. 真/假?在 ThingsBoard 仪表板中无法更改小部件的背景颜色?

A. 假

  1. 你如何改变仪表板模拟表的范围?

A. 通过在高级选项卡下将最小值更改为0,最大值更改为100

  1. 真/假?从print(data)行返回的信息无法被人类阅读?

A. 假

  1. 我们从RobotDance类中调用哪个方法让 T.A.R.A.S 跳舞?

A. lets_dance_incognito方法

  1. 真/假?我们需要使用的处理json数据的库叫做jason

A. 假

  1. 我们如何在仪表板上创建一个开关?

A. 点击RobotControl仪表板,点击橙色的铅笔图标,点击+图标,点击创建新小部件图标,选择控制小部件,然后点击开关控制。

  1. 真/假?T.A.R.A.S 上的绿色 LED 连接到 GPIO 引脚 14。

A. 假

  1. 真/假?一个发布者只能有一个订阅者。

A. 假

  1. msgon_message方法返回多少个键值对?

A. 两个

第十七章

  1. 我们可以使用哪个程序(平台)在本地安装 MQTT Broker?

A. Mosquitto

  1. 真/假?JavaScript 和 Java 是相同的技术?

A. 假

  1. 真/假?我们可以使用 JavaScript 创建一个 MQTT 客户端?

A. 真

  1. 我们可以使用google-api-javascript-client库访问哪些谷歌服务?

A. 谷歌云服务

  1. 真/假?MQTT 是物联网中使用的协议?

A. 真

  1. JavaScript Node.js 技术允许你做什么?

A. 允许在浏览器之外执行 JavaScript。

  1. 真/假?Python 可以用来开发 MQTT 客户端?

A. 真

  1. 真/假?我们可以通过使用脚本标签在我们的网页中添加外部 JavaScript 库的功能。

A. 真

  1. 我们如何在 JavaScript 代码中为我们的 MQTT 客户端设置用户名和密码?

A. 通过实例化一个Paho.MQTT.Client

  1. 真/假?我们可以在 Cloud MQTT 应用程序中查看我们发布的消息?

A. 真

第十八章

  1. 我们的项目中发布控制类型消息的主题是什么?

A. RobotControl

  1. 真/假?MQTT Broker 和 MQTT Server 是用来描述同一件事情的词语?

A. 真

  1. 真/假?T.A.R.A.S 在 MQTT 相同的主题上发布和订阅?

A. 假

  1. 我们的 HTML JavaScript 客户端上大的前进和后退按钮的颜色是什么?

A. 紫色

  1. 真/假?使用 HTML JavaScript 客户端,我们可以远程使用 T.A.R.A.S 相机拍照?

A. 真

  1. 我们用来订阅来自 T.A.R.A.S 的distance数据的 MQTT 主题的名称是什么?

A. RobotEyes

  1. 真/假?我们的 HTML JavaScript 客户端包含屡获殊荣的 UI 设计?

A. 假

  1. 真/假?使用我们的 CloudMQTT 帐户,我们可以查看我们实例上发布的消息?

A. 真

  1. 我们用来从 T.A.R.A.S 实时传输视频的技术的名称是什么?

A. RPi-Cam-Web-Interface

  1. 真/假?Johnny-Five 是可口可乐公司推出的一种新果汁饮料的名称?

A. 在撰写本文时,答案是假。

posted @ 2025-09-22 13:19  绝不原创的飞龙  阅读(68)  评论(0)    收藏  举报