JavaScript-机器人实用指南-全-
JavaScript 机器人实用指南(全)
原文:
zh.annas-archive.org/md5/3adc004858ecd1170c482574bf5ccbc2
译者:飞龙
前言
在硬件和嵌入式设备编程中使用 JavaScript 的使用量急剧增加。JavaScript 有一套有效的框架和库,支持机器人生态系统。
使用 JavaScript 进行动手机器人编程从设置一个用于用 JavaScript 编程机器人的环境开始。然后,您将深入构建基本级别的项目,如跟随线机器人。您将完成一系列项目,这些项目将教会您关于 Johnny-Five 库的知识,并在每个项目中提高您的技能。随着您通过章节的进展,您将创建一个闪烁的 LED,然后转向传感器和其他更高级的概念。然后,您将构建一个高级级别的 AI 机器人,将他们的 NodeBots 连接到互联网,创建一个 NodeBots 集群,并探索 MQTT。
在本书结束时,您将通过使用 JavaScript 构建机器人获得实际操作经验。
本书面向的对象
使用 JavaScript 进行动手机器人编程适合那些有树莓派 3 使用经验并且喜欢用 JavaScript 编写草图的人。对 JavaScript 和 Node.js 的基本了解将帮助您充分利用本书。
本书涵盖的内容
第一章,设置您的开发环境,本章涵盖了树莓派及其使用和设置方法。这包括在 SD 卡上设置 Raspbian,安装 Node.js,安装 Johnny-Five 库,以及安装 Raspi-IO 库。本章还将解释 Johnny-Five 和 Raspi-IO 的总体概念,以及使用 JavaScript 进行机器人编程带来的好处。
第二章,创建您的第一个 Johnny-Five 项目,在本章中,读者将构建他们用于 Johnny-Five 项目的开发环境,并创建他们的第一个 Johnny-Five 项目:一个闪烁的 LED
第三章,使用 RGB LED 构建交互式项目,在本章中,读者将通过 LED 介绍数字和 PWM IO 引脚—they will create a couple of projects with multiple LEDs and an RGB LED and explore fully the Johnny-Five LED API. 我们还将探讨通过包含颜色库来控制 RGB LED 的颜色,从而包含外部 Node.js 库。
第四章,通过按钮引入输入,在本章中,我们将向用户展示如何通过按钮将基本输入集成到他们的项目中。读者将学习通过使用 Johnny-Five 按钮事件来跟踪按钮。
第五章,使用光传感器创建夜灯,在本章中,我们将添加一个传感器并创建我们的第一个实际项目:夜灯!夜灯将通过读取光传感器来工作,如果房间明亮,则关闭 LED 灯,但如果房间昏暗,则点亮它!我们还将讨论传感器在机器人项目生态系统中的作用。
第六章,使用电机移动你的项目,在本章中,我们将讨论如何使用电机使你的项目移动。这包括你将需要的额外硬件,以确保你的项目能够正确供电,如何将电机连接到你的项目,johnny-five 电机 API,以及在使用 Raspberry pi 时使用电机时常见问题的故障排除。
第七章,使用伺服器进行精确运动,在本章中,我们将讨论使用伺服器在机器人项目中实现精确运动,并构建一个对传感器做出响应的伺服器。读者将了解伺服器和 Johnny-Five 伺服器 API,并构建一个包含伺服器的项目。他们还将了解在制作移动机器人项目时伺服器和电机的区别。
第八章,动画库,动画库是通过对速度、加速度曲线以及伺服器运动的起始点和结束点进行控制,以精细调整对 johnny-five 伺服器项目的控制的一种好方法。在本章中,我们将研究动画库,并介绍如何控制精确的伺服器运动。
第九章,获取所需信息,在本章中,我们将探讨为什么你想将你的 NodeBots 项目连接到互联网。我们将从查看你可以使用 GET 请求从网站获取信息的方式开始;比如你所在地区的天气预报。我们将使用天气数据和 RGB LED 构建我们的第一个互联网连接机器人。
第十章,使用 MQTT 与互联网上的事物进行通信,在本章中,我们将讨论 MQTT,这是一种常见的物联网通信协议。我们将研究使用 MQTT 代理,用我们的 NodeBot 订阅它,并使用这些数据实时做出反应。
第十一章,构建 NodeBots 集群,在最后一章中,我们将探讨继续你的 NodeBots 之旅的可能路径。我们将研究如何将前几章中开发的技能应用于构建多个连接的 NodeBots,以及探索 Johnny-Five 库中留下的探索途径。
为了充分利用这本书
为了构建书中包含的所有项目,你需要以下基本物品:
-
带有任意操作系统的笔记本电脑
-
Raspberry Pi3
-
Micro SD 卡(至少 8 GB)
-
面板和电线
-
Adafruit DC 和步进电机 HAT for Raspberry Pi - 迷你套件
-
Raspberry Pi 3 T-Cobbler
-
GPIO 扩展板
-
将 LCD 连接到您的 Pi
-
直流玩具/业余电机 - 130 尺寸
-
4 x AA 电池盒带开关
关于要求的更多细节,硬件和软件的设置在每个章节的“技术要求”部分都有解释。
下载示例代码文件
您可以从 www.packtpub.com 的账户下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
在 www.packtpub.com 上登录或注册。
-
选择“支持”选项卡。
-
点击代码下载和勘误。
-
在搜索框中输入书籍名称,并遵循屏幕上的说明。
文件下载完成后,请确保使用最新版本的以下软件解压缩或提取文件夹:
-
WinRAR/7-Zip for Windows
-
Zipeg/iZip/UnRarX for Mac
-
7-Zip/PeaZip for Linux
本书代码包也托管在 GitHub 上,网址为 github.com/PacktPublishing/Hands-On-Robotics-with-JavaScript
。如果代码有更新,它将在现有的 GitHub 仓库中更新。
我们还提供其他代码包,这些代码包来自我们丰富的书籍和视频目录,可在 github.com/PacktPublishing/
上找到。查看它们!
下载彩色图像
我们还提供了一份包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:www.packtpub.com/sites/default/files/downloads/HandsOnRoboticswithJavaScript_ColorImages.pdf
。
使用的约定
本书使用了多种文本约定。
CodeInText
:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账号。以下是一个示例:“将下载的 WebStorm-10*.dmg
磁盘镜像文件挂载为系统中的另一个磁盘。”
代码块设置如下:
board.on("ready", function() {
// Everything else goes in here!
});
任何命令行输入或输出都应如下所示:
sudo node implicit-animations.js
粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“要设置一个源,请在左侧菜单中选择“源”。”
警告或重要注意事项看起来像这样。
小贴士和技巧看起来像这样。
联系我们
欢迎读者反馈。
一般反馈:请发送电子邮件至 feedback@packtpub.com
,并在邮件主题中提及书籍名称。如果您对本书的任何方面有疑问,请发送电子邮件至 questions@packtpub.com
。
勘误:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告这一情况。请访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。
盗版:如果您在互联网上遇到任何形式的我们作品的非法副本,我们将不胜感激,如果您能向我们提供位置地址或网站名称。请通过发送链接至 copyright@packtpub.com
与我们联系。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且对撰写或参与一本书籍感兴趣,请访问 authors.packtpub.com.
评论
请留下评论。一旦您阅读并使用了这本书,为何不在您购买它的网站上留下评论呢?潜在读者可以查看并使用您的客观意见来做出购买决定,我们 Packt 可以了解您对我们产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!
如需了解更多关于 Packt 的信息,请访问 packtpub.com.
第一章:设置您的开发环境
欢迎您!本书旨在帮助您开始使用 Raspberry Pi、Node.js 和 Johnny-Five 框架用 JavaScript 编写机器人代码。本章将详细介绍 Raspberry Pi 是什么以及我们将如何使用它,并帮助您准备好开发环境。
本章将涵盖以下主题:
-
什么是 Raspberry Pi
-
我们将如何使用 Raspberry Pi
-
安装操作系统
-
设置 SSH 和硬件接口
-
安装 Node.js
-
安装 Johnny-Five 和 Raspi-IO
技术要求
为了开始,您需要以下物品:
-
一个 Raspberry Pi 3:原始型号或 B 型号都可以。
-
电源供应:将 Raspberry Pi 插入电脑的 USB 端口可能会导致严重问题,因为它无法提供足够的电力以允许 Raspberry Pi 正常工作,因此您需要一个合适的墙壁电源适配器。
-
MicroSD 卡:这至少需要 8GB 来存储 Raspbian 操作系统和我们将要编写的代码。您还需要一种从电脑写入 SD 卡的方法——要么是完整的 SD 卡适配器,要么是 USB 卡读卡器。
-
一个 PC9685 GPIO 扩展板:有一些需要组装的扩展板在 Adafruit 上(
www.adafruit.com/product/815
),但如果您对焊接没有信心,那么在亚马逊上搜索PC9685
可以找到许多预组装的。 -
文本编辑器:您的代码编辑器没问题;我们只需要在将操作系统镜像烧录到 SD 卡上后编辑几个文件。
如果这是您第一次涉足硬件项目,我建议您购买至少包含以下项目的套件,因为它将帮助您完成本书中的许多项目,并为您提供创建您自己的设计的部件:
-
Pi Cobbler
-
电阻
-
LED 灯
-
一个伺服电机
-
一个电机
-
按钮
-
其他传感器和外设
以下是一些这些物品的好例子(在撰写本文时):
-
Raspberry Pi 3 B+入门套件:
www.sparkfun.com/products/14644
-
Adafruit Raspberry Pi 3 Model B 入门套件:
www.adafruit.com/product/2380
-
如果您已经有了 Pi,他们还出售不含 Pi 的套件:
www.adafruit.com/product/3241
什么是 Raspberry Pi?
因此,现在您有了这个绿色、信用卡大小的物体,上面有一堆您认识的端口和一些引脚,如图所示。您可以看到一些芯片和一些您可能不认识的部件。在我们讨论这个相当不起眼的板上的电力之前,我们需要澄清一些我们将全书使用的词汇:
微控制器
微控制器是一个包含一系列设备的术语。它用来描述一个包含处理器、内存和输入/输出外围设备(或与这些外围设备交互的方式)的设备,该设备旨在执行特定类型的任务。一个极其常见的微控制器是 Arduino Uno,而树莓派在技术上也被归类为这一类别。
通用输入/输出(GPIO)引脚
微控制器通过发送和接收通过为输入和/或输出信号设计的引脚发送的电信号与传感器、LED 和按钮等设备进行接口。这些引脚可以分为多个子类别,正如我们在后续章节中将了解到的那样,但你可以将它们整体称为 GPIO 引脚。我们将在整本书中使用这个缩写。
Debian 和 Raspbian
Debian 是一个针对 Linux 新手的非常用户友好的 Linux 发行版。它包含了许多在 Linux 预装时常用的实用程序,并且与许多你可能会用于计算机的外围设备兼容,例如 Wi-Fi 网卡和 USB 设备。
Raspbian 是专门为在树莓派设备上运行而修改的 Debian 版本。它为树莓派的 GPIO 引脚、USB Wi-Fi 设备和扩展槽提供了驱动程序,这些驱动程序允许你连接特定的显示屏和摄像头。
Raspbian 有两种版本——Raspbian Full 和 Raspbian Lite。Full 版本包含图形桌面,有针对教育编程和开发的程序。Lite(我们将用于本书的项目)只有命令行界面,但在使用树莓派外围设备时仍然具有完整的功能。截至本书编写时,Raspbian 的当前版本是 4.14,昵称为Stretch。
Johnny-Five 和 Raspi-IO
回到 2012 年,Rick Waldron 编写了一个名为node-serialport的程序来操作 Arduino Uno,并围绕它创建了一个名为 Johnny-Five 的库。从那时起,Johnny-Five 库已经发展到超过 100 位贡献者,可以控制超过 40 个平台,包括树莓派!它还可以控制许多类型的传感器和外围设备,你可以使用这些设备在 Node.js 中创建你梦寐以求的机器人项目!
Johnny-Five 库能够支持如此多的平台之一的方式是通过创建所谓的 IO 插件。你为希望控制的每种类型的板创建一个 IO 插件。例如,我们将安装并使用 Raspi-IO 插件来使用 Johnny-Five 与树莓派。
这个系统的优点在于,你在本书中编写的代码可以在 Johnny-Five 支持的任何其他平台上使用(你只需要更改引脚号!)当你使用相同的 API 为任何可能使用的设备编写代码时,为 Node.js 机器人网络编写代码要容易得多。
因此,树莓派在技术上是一个微控制器...
让我们回到树莓派是什么的问题。简而言之,它是一个微控制器。它拥有数十个 GPIO 引脚,可以用来与许多物理外围设备接口,以实现特定的任务。低成本和小尺寸使得树莓派成为一个多功能的设备,但它的功率允许你用它来完成其他微控制器可能无法胜任的任务。
...但它也是一个计算机!
关于树莓派的一个有趣的事实是,虽然它是一个微控制器,但它也可以用作一台完整的计算机!虽然它当然不是最强大的硬件,但安装了完整的 Raspbian 系统后,连接到显示器、键盘和鼠标的树莓派可以成为孩子们和成人学习编程的绝佳机器!树莓派最初的目的是创建一个低成本的教育机器来教授编程,它在这一点上远远超出了所有人的预期。它同时也是为创客世界提供的一款优秀的微控制器,这是一个巨大的额外优势!
我们将如何使用树莓派
因此,我们已经确定树莓派(Raspberry Pi)对于其体积来说是一个非常灵活且强大的机器,但鉴于有如此多的选择,确定从哪里开始可能会有些困难。幸运的是,我们有一个计划,将引导你完成你的第一个树莓派和 Johnny-Five 项目,这样你就可以跟上进度,同时也会赋予你构建高级机器人项目的能力。
充分利用树莓派所能提供的一切!
我们将要构建的项目将利用树莓派既是微控制器又是计算机的事实。我们将通过 Raspbian 发行版使用 Linux 操作系统,并利用它来在 Node.js 中运行我们的项目。我们还将使用 Johnny-Five 和 Raspi-IO 来利用树莓派的 GPIO,以便以易于理解和可移植到许多不同硬件平台的方式创建机器人项目。
Johnny-Five – 让我们能够在 Node.js 中编写硬件
在过去,当你想到机器人项目时,意味着用 C 或 C++编写代码,通常是通过 Arduino IDE 和 API。然而,随着微控制器的变得更加强大,它们能够运行其他编程语言,甚至是脚本语言,如 Python 和 JavaScript 的子集。
当然,有了像树莓派这样的计算机/微控制器混合体,你能够运行标准的 Node.js,这样你就可以在不处理任何底层语言的情况下创建甚至高级的机器人项目。能够在 Node.js 中编写机器人项目有很多好处:
-
基于事件的系统:在 Arduino 和 C/C++ 级别的机器人编程中,你需要通过循环的每次迭代检查每个事物的状态,并相应地采取行动。这可能会创建出庞大的函数和代码路径。使用 Node.js 和 Johnny-Five,我们可以使用事件发射器和系统,它们与实际世界的传感器读取和外设交互非常契合,因为实际世界中的事物需要时间。这将帮助你以反映世界异步工作方式的方式组织代码。
-
垃圾回收/自动内存管理:虽然 Arduino 和 C++ 会为你处理大部分内存管理,但在使用 C 的微控制器编程中,需要严格的内存管理。虽然你有时需要考虑 Raspberry Pi 的资源限制,但这比 20K SRAM 的时代要容易得多。
-
使用你已知的语言:与其试图记住新语言中事物的工作方式,不如专注于一次学习少量内容,这将加速你在电子和机器人领域的学习。使用 Node.js 可以帮助你专注于学习电子的广泛而多样的世界,而不是额外的工作,即记住它是
uint8_t
还是uint16_t
。
安装操作系统
为了开始使用 Johnny-Five 和 Raspberry Pi,我们需要通过将其烧录到 microSD 卡上来设置 Raspbian 操作系统。然后,我们需要编辑 SD 卡上的某些文件,以便 Raspberry Pi 能够带 Wi-Fi 和 SSH 功能启动。最后,在安装 Node.js、Johnny-Five 和 Raspi-IO 之前,我们需要启动 Raspberry Pi 并设置一些设置。
下载 Raspbian Lite
以下步骤将向您展示如何下载 Raspbian Lite:
- 第一步是下载 Raspbian Lite 镜像,这样我们就可以将其烧录到我们的 microSD 卡上。获取镜像的最佳地点是
www.raspberrypi.org/downloads/raspbian/
,如下面的截图所示:
Raspbian 下载页面的截图,包含完整版和精简版下载链接
- 选择 RASPBIAN STRETCH LITE(或当前版本),这将替换掉单词 STRETCH。给自己一些时间来完成这一步;虽然 Raspbian Lite 比完整的 Raspbian 小得多,但它仍然有几百兆字节,下载可能需要一些时间!
如果你正在准备使用 Raspberry Pi 和 Raspbian 运行一个班级、黑客马拉松或其他活动,最好是预先下载并将其放置在闪存驱动器上,以便分发,因为会议和活动的 Wi-Fi 可能比正常情况下慢,甚至可能断断续续,所以要做好准备!
将镜像烧录到 SD 卡中
幸运的是,将 OS 镜像烧录到 SD 卡的工具已经从复杂的命令行工具发展而来,这些工具可以像烧录 SD 卡一样轻松地覆盖您的电脑硬盘。我目前最喜欢的工具是Etcher,您可以在etcher.io/
下载,以下步骤所示:
-
免费版已经足够满足我们的需求,因此下载并安装它,就像下载 Raspbian Lite 一样。
-
下载完两者后,您需要将 micro SD 卡放入您的电脑中,无论是将其放入全尺寸 SD 适配器然后插入电脑的插槽,还是使用 USB-to-micro-SD 适配器。无论您使用哪种方式,在继续之前,请确保您的电脑可以看到该卷作为驱动器。然后,启动 Etcher。以下截图显示了 Etcher 在 Mac 上的运行情况:
Etcher 程序在 Mac 上运行的截图
- 一旦您看到与前面截图类似的窗口,您需要选择您刚刚下载的 Raspbian Lite 镜像。您甚至不需要解压
.zip
文件——Etcher 可以直接处理!选择镜像后,只要您的机器可以看到它,Etcher 应该会自动选择您的 micro SD 卡驱动器!一旦您确认镜像和 micro SD 卡驱动器已正确选择,点击“Flash!”开始烧录过程。
有时,对于较大的 micro SD 卡,Etcher 会发出关于驱动器非常大的警告(当我使用 64 GB 卡时就会发生这种情况)。这是为了防止您覆盖您的电脑硬盘。您可以通过确认窗口绕过警告——但请务必确保首先选中您的 micro SD 卡驱动器!
几分钟后,烧录器会将镜像烧录到您的 micro SD 卡上,然后验证镜像是否已存在于卡上。完成此操作后,拔出并重新插入 micro SD 卡,以便您的电脑将其识别为驱动器;驱动器应命名为boot
。我们还没有完全编辑完镜像文件,因此 Etcher 礼貌地尝试弹出 micro SD 卡驱动器时需要忽略。
编辑 SD 卡上的文件
我们需要编辑和创建一些文件在我们的 Raspberry Pi 镜像中,以便在打开 Raspberry Pi 时能够通过 SSH 访问它。首先,我们将使用以下步骤设置 Wi-Fi:
如果您使用以太网线和端口将 Raspberry Pi 连接到互联网,您可以跳过此步骤。如果这不能使 Wi-Fi 工作,您需要查看“启动 Pi”部分下的信息框以获取故障排除步骤和设置此设置的替代方法(如果更复杂)。
- 为了设置 Wi-Fi,您需要在 micro SD 卡驱动器的根目录下创建一个名为
wpa_supplicant.conf
的文件,其中包含以下文本:
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
ssid="yourNetworkSSID"
psk="yourNetworkPasswd"
}
- 将
yourNetworkSSID
替换为您要连接的 Wi-Fi 网络的 SSID,然后将yourNetworkPasswd
替换为该 Wi-Fi 网络的密码。
在撰写本文时,Raspberry Pi 的 Wi-Fi 芯片只能连接到 2.4 GHz 网络,所以你需要确保你输入一个在该带宽上运行的网络,否则你的 Raspberry Pi 将无法连接!
- 在你设置好 Wi-Fi 网络后,你将想要告诉 Raspberry Pi 允许你通过 SSH 连接到它。为此,你需要在
wpa_supplicant.conf
文件相同的root
文件夹中创建一个名为ssh
的文件。确保ssh
文件为空且没有扩展名。完成所有操作后,micro SD 卡驱动器的root
目录将类似于以下截图:
编辑后 micro SD 驱动器上的文件列表
一旦完成所有这些,完全弹出 microSD 驱动器,取出 microSD 卡并将其插入 Raspberry Pi。我们现在可以启动 Raspberry Pi 并开始安装软件。
启动 Pi
将 micro SD 卡插入后,将电源连接到 Raspberry Pi。一个红色和一个绿色的 LED 应该会亮起。红色 LED 应该是稳定的——这是电源指示灯。绿色灯会闪烁——这是指示活动的 LED。Raspberry Pi 在插入电源后最多需要一分钟来启动。接下来,我们将 SSH 连入。
从 Linux 或 Mac 进行 SSH 连接
如果你在一台 Mac 或 Linux 机器上,你将打开一个终端并输入以下内容:
ssh pi@raspberrypi.local
如果你成功,你会看到一个询问主机真实性的问题出现。通过输入 yes
并按 Enter 来回答。然后,你会被要求输入密码,密码是 raspberry
,如以下截图所示:
从 Mac 终端成功 SSH 进入 Raspberry Pi
输入密码后,你应该会看到以下内容:
从 Mac 通过 SSH 成功登录 Raspberry Pi
从 Windows 进行 SSH 连接
为了从 Windows 机器上 SSH 连接,你需要使用一个名为 PuTTY 的程序。你可以在 putty.org/
获取它。但首先,你需要 Raspberry Pi 的 IP 地址。你需要一个显示器、一根 HDMI 线和一个 USB 键盘。一旦你有了这些,请按照以下步骤操作:
-
在启动 Raspberry Pi 之前,将显示器、HDMI 线和 USB 键盘连接到 Raspberry Pi 。然后插入电源。
-
当它提示你输入用户名时,输入
pi
。当它要求输入密码时,输入raspberry
。一旦你登录,输入ifconfig
。你应该会看到大量信息出现。 -
在
wlan0
部分下方查找inet
地址。对于以下输出,IP 地址是192.168.1.106
,如以下截图所示。将此 IP 地址记下来。然后,你可以拔掉显示器和键盘——你不会再需要它们了:
从终端获取 IP 地址
- 一旦你有了树莓派的 IP 地址,你就可以启动 PuTTY。打开的窗口是配置窗口,如下面的截图所示:
PuTTY 配置窗口
- 将你获得的 IP 地址输入到标记为“主机名(或 IP 地址)”的字段中,然后点击“打开”按钮。你将被询问关于主机真实性的问题(仅在你第一次连接时)。选择“是”。然后当提示时,输入
Pi
作为用户名,raspberry
作为密码。完成这些后,你应该会看到以下内容:
使用 PuTTY 成功登录树莓派
现在每个人都已登录,让我们为我们的项目设置树莓派!
设置密码和硬件接口
现在我们已经将树莓派连接到 Wi-Fi 并 SSH 登录,在安装 Node.js 和开始编码之前,我们需要做一些更改。
首先,更改你的密码!
当你登录时,你的树莓派会警告你,使用默认用户名和密码启用 SSH 并不非常安全,这是绝对正确的!第一步是更改我们的密码。
为了做到这一点,在你的 SSH 窗口中,输入passwd
并按Enter键。你将被提示输入当前密码(raspberry
)和新密码。输入你喜欢的任何密码(只是不要忘记它!)然后你会被要求确认,哇!新密码已设置,如下面的截图所示。你的树莓派将更加安全:
更改 Pi 密码
更新树莓派
接下来,通过运行以下命令确保树莓派已更新并准备好使用:
sudo apt-get update && sudo apt-get upgrade
这将花费一些时间,但确保一切正确更新是值得的。
打开硬件接口
接下来,我们将设置树莓派,以便我们的硬件代码可以运行。运行以下命令:
sudo raspi-config
你将看到一个带有许多不同选项的图形菜单,如下面的截图所示:
raspi-config 菜单
你将想要使用箭头键选择接口选项
,然后选择I2C
和是
来打开它。重复 SPI,然后使用Tab键关闭菜单。当它提示你重启时,说是
,然后 SSH 重新登录,因为你要准备安装 Node.js、Johnny-Five 和 Raspi-IO!
安装 Node.js、Johnny-Five 和 Raspi-IO
因此,现在我们的 Raspbian 操作系统已安装并设置好,是时候安装 Node.js(它捆绑了 npm)、Johnny-Five 和 Raspi-IO 了!
安装 Node.js 和 npm
在过去,你必须在 Pi 上编译 Node.js 源代码,由于 Raspberry Pi 使用的 ARM 处理器没有二进制文件,因此成功率各不相同。幸运的是,由于过去几年第三方的大力支持,你现在可以从 nodejs.org/en/
网站轻松下载二进制文件!但我们如何从 Raspberry Pi 的命令行中完成这个操作呢?
检测你的 ARM 处理器版本
如果你使用的是本书推荐的 Raspberry Pi 3 Model B,你很可能在 ARM v8(Raspberry Pi 3 原本是 ARMv7,这也很好!)上。但你应该始终检查(如果你使用的是不同的 Raspberry Pi,如 Pi Zero 或 Pi 2/1 系列,则应加倍检查)。要在你的 Raspberry Pi 上检查 ARM 版本,请在 SSH 终端中运行以下命令:
uname -m
你将看到一个类似 armv#
的返回消息,其中 #
是一个数字(可能后跟一个字母)。这个数字很重要,因为它告诉我们需要哪个 Node.js 二进制文件。一旦你有了你的 ARM 版本,请按照以下步骤操作:
- 前往 Node.js 下载页面
nodejs.org/en/download/
,如下截图所示:
Node.js 二进制下载页面的快照
- 右键单击你需要的 ARM 版本链接并复制 URL。然后,在你的 Raspberry Pi 的 SSH 终端中运行以下命令:
wget <binary-download-url>
将 <binary-download-url>
(包括尖括号)替换为你从 Node.js 下载网站复制的 URL。下载完成后,我们需要使用以下代码提取存档:
tar -xvf node-v****-linux-armv**.tar.xz
- 星号将根据 Node.js 的当前 LTS 版本和你的 ARM 版本而有所不同。Raspberry Pi 将在控制台输出大量的文件名,然后返回你的 shell 提示符。这意味着二进制文件已经被提取到你的
home
文件夹中。我们需要将它们放入/usr/local
文件夹。为此,请运行以下命令:
cd node-v****-linux-armv**
sudo mv ./lib/* /usr/local/lib
sudo mv ./share/* /usr/local/share
- 这将把所有预编译的二进制文件移动到你的 Raspberry Pi 上的新位置。一旦完成,运行以下命令:
node -v
npm -v
你应该会看到以下类似的内容:
成功的 Node.js 安装结果
- 如果一切顺利,你现在已经安装了 Node.js 和 npm!让我们用 Johnny-Five 和 Raspi-IO 来结束这个话题!请注意,你可以通过运行以下命令来完全清理二进制下载:
cd ~
rm -rf node-v**-linux-armv**
rm -rf node-v****-linux-armv**.tar.xz
一些有更多 Debian 经验的人可能会问,为什么我们不能直接使用 apt-get?简短的回答是,名为 node
的软件包很久以前就被占用了,因此,由于这种情况,以及 sudo apt-get install nodejs
已经过时(在撰写本文时,使用此命令将安装 v4
,而不是我们需要的 v8+
,如果它安装 Node.js 的话),我们需要下载二进制文件并自己移动它们。
安装 Johnny-Five 和 Raspi-IO
要安装 Johnny-Five,一旦你确认已经安装了 Node.js 和 npm,运行以下命令:
npm i -g johnny-five raspi-io
这将全局安装库;你不需要在每个新项目中重新安装它。就这样!你现在可以开始使用 Johnny-Five 在树莓派上开发 Node.js 机器人项目了!
摘要
感觉好像有很多,但现在你已经完成了构建这本书中项目所需的完整开发环境,并且你已经迈出了用 JavaScript 构建机器人的第一步。你了解了更多关于树莓派是什么以及为什么我们使用它,以及如何准备操作系统镜像!
问题
-
我们将在本书的项目中使用哪种常见的树莓派操作系统,以及它是基于哪个 Linux 发行版的?
-
GPIO 代表什么?
-
谁最初启动了 Johnny-Five 项目,他们用它来控制什么?
-
你在树莓派上运行什么命令来找出它使用的 ARM 架构?
-
为什么更改树莓派的默认密码很重要?
-
使用 JavaScript 和 Node.js 进行机器人代码有哪些好处?
-
为什么我们必须下载 Node.js 的二进制文件而不是使用 Raspbian 的包管理器?
进一步阅读
你可以使用以下资源进一步阅读本章涉及的主题:
-
从树莓派组织的网站了解更多关于树莓派的信息:
www.raspberrypi.org/
-
从 Johnny-Five 的主项目页面了解更多关于 Johnny-Five 的信息(随着我们使用他们的文档来完成本书的许多项目,我们将看到很多这个网站):
johnny-five.io/
-
从 Raspbian 网站了解更多关于 Raspbian 操作系统的信息:
www.raspberrypi.org/documentation/raspbian
第二章:创建您的第一个 Johnny-Five 项目
现在我们已经设置了开发环境,是时候开始编写代码并让 LED 发光了!我们将从运行 Johnny-Five 机器人的“Hello World!”开始:让 LED 闪烁。在这个过程中,我们将了解如何导航 Johnny-Five 和 Raspi-IO API 文档,并检查 Johnny-Five 的事件系统。
本章将涵盖以下主题:
-
创建项目文件夹
-
安装 Johnny-Five 和 Raspi-IO
-
连接 LED
-
让 LED 闪烁
技术要求
您将需要第一章“设置开发环境”中设置的 Raspberry Pi,一个面包板,以及一个 Pi Cobbler 以方便引脚访问。您可以从 Adafruit、SparkFun 或 Amazon 购买 Pi Cobbler(有时也称为 Pi Wedge)。Pi Cobbler 也包含在第一章“设置开发环境”中推荐的套件中。
本章的示例代码在此:github.com/PacktPublishing/Hands-On-Robotics-with-JavaScript/tree/master/Chapter02
。
以下图显示了两种不同的 Raspberry Pi Cobbler,都来自 Adafruit。右侧的 Cobbler 连接了扁平电缆:
我们将在本章后面讨论如何设置 Cobbler。您还需要一个 LED、一些跳线或面包板电线,以及一个 330 欧姆的电阻。
如果您在问自己“电阻器是什么,它有什么作用?”,简短的解释是电阻器将阻止来自引脚的 5V 电流烧毁需要接近 3.3V 电流的 LED。有关电流、电压和电阻的更深入介绍,SparkFun 网站上有一些优秀的免费材料。您可以通过以下链接访问这些材料:
电流:learn.sparkfun.com/tutorials/what-is-electricity
电阻器:learn.sparkfun.com/tutorials/resistors
创建项目文件夹
我认为组织 Raspberry Pi 的最佳方式是将每个项目放在自己的文件夹中。在这本书的源代码中,我已经这样做了。但让我们一步一步地了解如何设置您自己的项目文件夹。首先,您需要创建文件夹本身。对于本章的项目,我们将称之为led-blink
,您需要运行以下命令:
cd ~
mkdir led-blink
确保您在 Raspberry Pi 的 SSH 会话中运行此命令,而不是在您的桌面上。
从现在开始,除非文本中直接说明要在您的桌面上运行某些内容,否则您应该在我们在第一章,“设置开发环境”中设置的 Raspberry Pi 的 SSH 会话中运行所有命令。
设置 npm 来管理我们的模块
我们将使用不仅仅是 Johnny-Five 和 Raspi-IO 来创建我们的项目,并且你希望能够通过你喜欢的 Git 托管服务移动你的代码,例如将其移动到新的 Raspberry Pi。为了使这个过程尽可能顺利,我们将确保 npm
能够准确地重新创建你的项目。为此,我们想要一个预先准备好的 package.json
文件。为此,导航到你的 project
文件夹并初始化它:
cd led-blink
npm init -y
npm init
命令中的 -y
选项告诉 npm
使用默认答案回答所有初始化问题。这对于只有你将使用的项目来说是可以的,但如果你打算将你的工作部署供他人使用,或者创建自己的 npm
模块,请确保相应地编辑你的 package.json
文件。
这些命令创建我们的 package.json
,这样当我们使用 --save
选项安装 npm
模块时,清单将更新,以便当你移动你的项目时,有一个完整的依赖项记录。
开始使用 Johnny-Five 和 Raspi-IO
现在我们项目文件夹已经准备好依赖项,我们将开始探索 Johnny-Five 和 Raspi-IO 文档,这些文档将帮助我们创建本书中的项目。
收集资源和文档
我们将使用以下两个主要来源的文档来为本书中的项目:
-
Johnny-Five 网站:
johnny-five.io/
-
Raspi-IO GitHub README 和 wiki:
github.com/nebrius/raspi-io
以下截图显示了 GitHub 上的 Raspi-IO README:
我们将使用 johnny-five.io
上的 Johnny-Five 文档来查找 API 调用和其他关于 Johnny-Five 库的信息,以及用于 Raspberry Pi 特定信息的 Raspi-IO README,包括引脚编号。
查看 LED 闪烁项目
我们首先需要的是来自 Raspi-IO README 的内容:我们将阅读并运行他们的 led-blink
代码作为我们的 Hello World! 让我们看看代码块:
const Raspi = require('raspi-io');
const five = require('johnny-five');
const board = new five.Board({
io: new Raspi()
});
board.on('ready', () => {
// Create an Led on pin 7 (GPIO4) on P1 and strobe it on/off
// Optionally set the speed; defaults to 100ms
(new five.Led('P1-7')).strobe();
});
这看起来不多,但这里发生了很多事情!前两行使用 require
引入 johnny-five
和 raspi-io
模块。然后,我们开始构建一个 board
对象,并将一个新的 raspi-io
模块实例作为其 I/O 传递。这是我们告诉 Johnny-Five 库我们在 Raspberry Pi 上运行代码的方式。
接下来,我们将在 ready
事件上设置我们的板对象的事件监听器。根据 Johnny-Five 文档,此事件在 板实例对象完成任何必须在程序运行之前进行的硬件初始化后触发。这意味着你不应该在事件处理器之外运行任何与机器人相关的代码,因为你不能确定你的板是否已准备好接收硬件命令。
从第 10 行开始的注释非常有帮助,因为它们告诉我们如何连接我们的 LED。我们将使用引脚 7(GPIO 4)——这意味着从顶部起的第七个物理引脚,被指定为 GPIO 4。
Raspberry Pi 引脚编号
引脚 7...标记为 GPIO 4?这很令人困惑!幸运的是,有许多免费的引脚图可以帮助我们翻译,如下面的图所示:
一个 GPIO/引脚图(来源:raspberrypi.org)
此外,Raspi-IO 库将接受许多相同的引脚名称,如 wiki 中方便的转换表所示:
来自 Raspi-IO wiki 的引脚表
当在 Raspberry Pi 上连接任何 Johnny-Five 项目时,保留这些引脚指南是很有帮助的。
连接 LED
现在我们已经通过了文档,并弄清楚哪里放什么,我们可以开始组装我们的 Raspberry Pi 项目。你需要你的 Raspberry Pi、Pi Cobbler、两根面包板电线、一个 LED(颜色不重要)和一个 300 欧姆电阻。
组装并连接 cobbler
为了确保 cobbler 放置正确,你需要确保当放置在 GPIO 引脚上时,扁平电缆从 Raspberry Pi 向外延伸,并且连接器侧面的小卡子面向 cobbler 的正确方向(这通常由扁平电缆插入的引脚周围的塑料墙确保;在施加过多压力之前,请确保它已对齐!)。
你希望将 cobbler 放置在至少半尺寸的面包板上,尽管我倾向于使用全尺寸的 Raspberry Pi 项目。确保 cobbler 上的两排引脚位于面包板中心凹槽的两侧,如下面的照片所示:
全尺寸(顶部)和半尺寸(底部)面包板上的 cobbler
新手使用面包板?SparkFun 网站上有一个关于它们如何工作的出色解释(以及一些有趣的趣闻),learn.sparkfun.com/tutorials/how-to-use-a-breadboard
。
连接电阻和 LED
你需要使用一根电线将 GPIO 4(引脚 7)连接到 330 欧姆电阻,并将电阻连接到 LED 的正极(长腿)。然后,你需要将负极(短腿)连接到地,或任何标记为 GND 的引脚。你的完成项目将看起来像这样:
你的 LED 项目
现在 LED 已经连接好了,是时候让它闪烁了!
让 LED 闪烁
为了让 LED 闪烁,我们需要在 Raspberry Pi 上安装代码,然后运行它!
将代码放置在 Raspberry Pi 上
如果你已经在你的桌面上编写了代码,需要将其传输到你的 Raspberry Pi,有几种方法可以做到:你可以在 macOS X 或 Linux 机器上使用rsync
:
rsync ./blink-led.js
pi@raspberrypi.local:~/<project folder>/
将 <项目文件夹>
替换为你想要转移到的文件夹(例如,书籍文件夹将是 hands-on-robotics-with-javascript/ch2/blink-led
)。
对于 Windows,请遵循在 winscp.net/eng/docs/ui_commander
安装和使用 WinSCP 的指南。
运行你的代码
一旦你的代码在 Raspberry Pi 上,你将想要切换到你的 SSH 会话并运行以下命令,如果你使用的是本书的源代码:
cd hands-on-robotics-with-javascript/ch2/blink-led
否则,使用 cd
命令进入你在 Raspberry Pi 上存储 blink-led.js
文件的文件夹。然后,运行以下命令:
sudo node blink-led.js
注意,Raspi-IO 插件要求你以 sudo
命令运行。如果一切顺利,你应该在你的面包板上看到一个快速闪烁的 LED。如果不这样,这里有一些故障排除步骤:
-
检查线路
-
仔细检查线路(真的,95% 的时间问题是线路问题)
-
确保在 Raspberry Pi 上 Node.js 脚本没有出现错误
摘要
恭喜!你已经制作了一个 Johnny-Five 机器人!在本章中,你学习了如何连接 LED,浏览 Johnny-Five 和 Raspi-IO 的文档,并在 Raspberry Pi 上运行你的代码!
问题
-
在 Johnny-Five 文档中查看,在 API 部分的 LED 节下查找
strobe
函数。第一个参数做什么?如果你将 500 作为第一个参数传递会发生什么? -
LED.strobe()
函数的第二个参数是什么?这对于等待 LED 关闭的应用程序有什么帮助? -
Johnny-Five 的 LED 对象是否发出任何事件?为什么,或者为什么不?
-
使用 Raspi-IO 文档,Raspberry Pi 的 P1-29 引脚在 GPIO #方面是如何翻译的?
-
使用 Johnny-Five 文档,命名一个与
LED.strobe()
函数同义的函数。 -
在 Johnny-Five 应用程序中,
ready
事件触发之前会发生什么?
进一步阅读
你可以查阅以下资源,以获取与本章节主题相关的进一步阅读材料:
-
Johnny-Five 文档:johnny-five.io
-
Johnny-Five GitHub 仓库:
github.com/rwaldron/johnny-five
-
Raspi-IO 库:
github.com/nebrius/raspi-io
第三章:使用 RGB LED 构建交互式项目
现在我们已经使用 Johnny-Five 和 Raspi-IO 构建了一个项目,现在是时候处理 GPIO 扩展器和 PWM 输出了,并使用 RGB LED 构建一个交互式项目。我们还将了解更多关于 Johnny-Five REPL 的信息,了解 PWM 引脚的工作原理,并使用这些知识从命令行控制 RGB LED。
本章将涵盖以下主题:
-
查看 LED 和 LED.RGB API
-
PWM 引脚和 GPIO 扩展器
-
带有颜色的其他节点包
-
Johnny-Five REPL
技术要求
您已经从第一章,设置您的开发环境中安装了本章所需的全部软件先决条件。您需要确保您的 Raspberry Pi 连接到互联网,并且已经使用您选择的方法 SSH 登录。
本章的示例代码可以在github.com/nodebotanist/hands-on-robotics-with-javascript/tree/master/ch3
找到。
对于硬件,您需要以下内容:
-
您的 Raspberry Pi
-
Cobbler/breadboard
-
Breadboard wires
-
PCA9685 GPIO 扩展板
-
RGB LED
-
330 欧姆电阻 x 3
查看 LED 和 LED.RGB API
在上一章中,我们简要地介绍了 Johnny-Five 中的 LED API,但在本章中,我们将深入探讨并讨论 PWM 输出以及标准 LED 的表亲 RGB LED——之所以这样命名,是因为它有一个红色、绿色和蓝色通道,可以复制数千种颜色。我们将使用 RGB LED 以及 Johnny-Five 中内置的一些更强大的工具,在本章中构建一个交互式项目。
LED 对象
LED 对象通常是人们首先查看 Johnny-Five 文档的对象。它也是一个很好的对象,可以用来概述对象文档的一般结构。让我们看一下每个部分,并了解我们稍后应该在哪里查找什么:
-
参数:本节讨论需要传递给对象构造函数的参数,以及它们需要采取的形式(顺序、对象键等)。
-
形状:这些是附加到构建对象上的字段,可能对用户编写代码有用。它们可以是只读的,如果是这种情况,会有标记。
-
组件初始化:这通常是一段示例代码,但它始终是对如何构建相关对象的常见用法的描述。如果有多个控制器用于特定组件,它们将使用每个控制器的示例进行列举。这对于我们的 GPIO 扩展板将很有用。
-
用法:这是一个示例代码,表示如何使用对象的最基本功能;对于 LED,这是
blink
函数,对于传感器,这将显示您通常用于从传感器获取读数的函数。 -
API:这是对对象可用的每个函数的完整文档,包括参数和预期结果。
-
事件:许多对象会发出事件(例如板上的
ready
事件);本节详细说明了它们何时会触发。 -
示例:Johnny-Five 社区是一个极好的示例来源,与所讨论对象相关的示例将被编目并链接在本节中。
花点时间熟悉 LED 文档 (johnny-five.io/api/led/
),因为 Led.RGB
对象实际上是 LED 对象的一个子类,并将继承其许多功能。
Led.RGB 对象
一旦你熟悉了 LED 对象,点击侧边栏中的 Led.RGB 链接,如下截图所示:
你将被带到 Led.RGB 文档页面。在 组件初始化 部分,查找 LED RGB PCA9685 部分。忽略布线图(它是为 Tessel 2,一个不同的微控制器),但请注意示例代码,如下所示:
new five.Led.RGB({
controller: "PCA9685",
pins: {
red: 2,
green: 1,
blue: 0
}
});
这是我们将用来初始化 RGB.LED
对象的代码。
我们还需要 API 部分,以便确定我们使用 RGB.LED
对象所需的函数和参数。特别看一下 color()
函数。
现在我们有了将颜色转换为 RGB 值的模块,我们可以开始讨论如何使用 RGB LED 将这些颜色引入我们的项目中。
PWM 引脚和 GPIO 扩展器
在我们连接并运行 RGB LED 项目之前,关于 PWM 引脚和 GPIO 扩展器的讨论是必要的,因为这些话题将影响你将要完成的绝大多数 Johnny-Five 项目。
PWM 引脚是如何工作的?
你并不总是想让 LED 以全亮度发光,尤其是在 RGB LED 的情况下,每个通道(红色、绿色和蓝色)的亮度决定了 LED 的感知颜色。大多数微控制器的引脚是数字的:它们在 5V 时为 HIGH,在 0V 时为 LOW。那么,你如何使用这些类型的引脚调整 LED 的亮度呢?答案涉及到平均电压和我们可以翻转数字引脚从 HIGH 到 LOW 的速度。
脉宽调制(PWM)引脚通过设置引脚 HIGH 和 LOW 的时间百分比来操作。以下截图显示了在短时间内以 50% 运行的引脚状态的示波器读数:
50% PWM 引脚的示波器读数
与 LED 闪烁开和关相比,这导致 LED 看起来以半亮度发光:这是因为人眼无法跟上状态变化的速度,看到的是 LED 是开着的,但变暗了。这有助于我们通过组合不同亮度的红、绿、蓝通道来使用 RGB LED 创建数千种不同的颜色。
当我们在 Johnny-Five 代码中设置颜色时,我们可以传递从0
到255
的值给红色、绿色和蓝色。这很好地与使用相同范围的 Web 十六进制颜色一起工作。一般来说,你可以设置 PWM 引脚从0
到255
(本书范围之外的某些例外除外)。
为什么我们需要 GPIO 扩展器
那么,为什么我们一开始就需要 GPIO 扩展器,当树莓派有这么多 GPIO 引脚时?这是因为 PWM 引脚需要计算资源和定时器,许多微控制器都有有限的硬件 PWM 引脚。你可以用软件模拟 PWM 引脚,但结果往往不可靠。例如,Arduino Uno 有八个 PWM 引脚。树莓派只有一个 GPIO 引脚,许多项目(包括本书后面包含的伺服和电机项目)将需要比一个更多的引脚,我们不希望使用软件 PWM。
这就是为什么我们使用 PCA9685 GPIO 扩展器的原因:它有 16 个专用的 PWM 引脚,并提供所有资源来运行它们。它使用名为 I²C(发音为eye-squared-see)的协议与树莓派通信,其细节超出了本书的范围,并且被封装在 Johnny-Five 组件对象中。如果你想了解更多关于 I²C 如何工作,请参阅进一步阅读部分。
连接我们的 GPIO 扩展器和 RGB LED
首先,你将想要将 PCA9685 扩展板连接到你的 PI 上:GND 连接到 GND,VCC 连接到 5V,SDA 连接到 SDA,SCL 连接到 SCL。接下来,从 cobbler 连接一个第二个 GND 引脚到面包板侧面的一个地线。然后是我们的 LED:长腿连接到地线。长腿一侧单独的腿是红色通道;将其连接到 PCA9685 板上的 0 列 PWM 行引脚。绿色和蓝色在另一侧;分别将它们连接到 PWM-1 和 PWM-2。完成所有这些后,你的项目应该看起来类似于以下:
本章项目的完成布线
引入其他 Node 包
Node.js 以创建小型、几乎微小的包而自豪,并且拥有出色的 npm 包管理器(以及其他工具)来帮助管理这些包。因为树莓派运行的是完整的 Node.js 版本,我们可以利用这一点来构建更有趣的项目。
项目 - 构建彩虹
你能立刻想起橙色 RGB 代码吗?我做不到。将我们已知颜色系统转换为 RGB(尤其是像红色、橙色和矢车菊蓝这样的名称)更容易记住。但与其构建一个为我们转换颜色的函数,我们不如利用我所说的 Stilwell 定律:如果你想到了,它可能已经在 npm 上了。不出所料,颜色模块将帮助我们。
使用color npm
模块
为了使用color npm
模块,我们首先需要安装它。在你的 SSH 会话中,在你的项目
文件夹中,运行以下代码:
npm i --save color
这也将保存颜色包到您的 package.json
中,以便于代码的可移植性。该模块导出一个函数,我们将使用它将像 red
或 #FF0000
这样的颜色字符串转换为表示 red
、green
和 blue
的整数数组。我们将使用这些值来设置我们的 RGB LED。以下是一个示例:
const Color = require('color')
let ledColor = Color('orange')
let ledRed = ledColor.red()
let ledGreen = ledColor.green()
let ledBlue = ledColor.blue()
我们将使用这个功能来帮助设置 Johnny-Five 程序中 RGB LED 的颜色。
启动我们的 Johnny-Five 代码
让我们结合我们对 Led.RGB
对象和颜色 npm 模块的了解,来创建一个基本的代码项目,我们将称之为 rgb-led-rainbow.js
:
const Raspi = require('raspi-io')
const five = require('johnny-five')
const color = require('color')
const board = new five.Board({
io: new Raspi()
})
board.on('ready', () => {
let rgbLED = new five.Led.RGB({
controller: "PCA9685",
pins: {
red: 0,
green: 1,
blue: 2
}
});
let colors = ['red', 'orange', 'yellow', 'green', 'blue', 'rebeccapurple']
let colorIndex = 0
let currentColor
setTimeout(() => {
currentColor = color(colors[colorIndex])
rgbLED.color([currentColor.red(), currentColor.green(), currentColor.blue()])
colorIndex++
if(colorIndex >= colors.length) {
colorIndex = 0
}
}, 1000)
})
此代码循环遍历 colors
数组中的颜色,并且每秒设置一次 RGB LED 的颜色,并向前移动,生成彩虹。
REPL - Johnny-Five 中的强大工具
调试我们的 LED 可能很棘手。在不重新布线的情况下,我们如何判断我们的绿色和蓝色通道是否颠倒,或者红色是否比其他通道亮得多?一个在调试 Johnny-Five 项目时非常有用的工具是 读-求值-打印循环(REPL)。
REPL 是如何工作的?
如果您以前使用过 Node.js、Python 或其他一些解释型语言,REPL 可能对您来说并不陌生。它允许您在运行时将语句写入 CLI 以直接从语言引擎生成结果。这在调试代码时非常有帮助,因为您可以在运行时查看并修改代码的状态。这在 Johnny-Five 中也是如此:REPL 允许我们插入 Johnny-Five 对象,因此我们可以查看并操作它们在运行时的状态。我们将使用这个功能来玩我们的 RGB LED 并从命令行控制它。
将我们的 RGB LED 添加到 REPL
请查看 Johnny-Five 文档中的 REPL;它位于 API 的 Board
组件部分。对我们来说重要的是 this.repl.inject()
,它接受一个对象,并使该对象的所有属性从 CLI 可访问。让我们修改我们的代码,通过使 rainbow
函数在设置 LED 之前检查布尔值,并将该布尔值和 RGB LED 组件对象添加到 CLI 中来利用 REPL:
const Raspi = require('raspi-io')
const five = require('johnny-five')
const color = require('color')
const board = new five.Board({
io: new Raspi()
})
board.on('ready', () => {
let rgbLED = new five.Led.RGB({
controller: "PCA9685",
pins: {
red: 0,
green: 1,
blue: 2
}
});
let colors = ['red', 'orange', 'yellow', 'green', 'blue', 'rebeccapurple']
let colorIndex = 0
let currentColor
let rainbowCycle = true
setTInterval(() => {
if(rainbowCycle) {
currentColor = color(colors[colorIndex])
rgbLED.color([currentColor.red(), currentColor.green(), currentColor.blue()])
colorIndex++
if(colorIndex >= colors.length) {
colorIndex = 0
}
}
}, 1000)
this.repl.inject({
rainbowCycle,
rgbLED,
color
})
})
现在,我们可以通过 Johnny-Five 在 Raspberry Pi 上运行此代码时提供的命令行 REPL 访问 LED 和控制彩虹循环的布尔值。
从命令行界面控制我们的 LED
将代码移动到您的 Raspberry Pi 上,并在 SSH 会话中,使用 cd
命令导航到您的 project
文件夹,然后运行您的项目(务必使用 sudo
!):
sudo node rgb-led-repl.js
然后,您可以操作 RGB LED 和颜色库来改变灯光的颜色。这里有一些您可以尝试的事情:
>> rainbowCycle = false // this stops the rainbow color cycle
>> rgbLED.off() // turns the RGB LED off
>> rgbLED.color(color('rebeccapurple').rgb().array()) // sets the LED purple!
摘要
在本章中,您使用 Raspberry Pi 和 Johnny-Five 创建了您的第一个交互式项目!我们首先探索了 LED 和 LED.RGB API,然后通过允许我们使用 npm
模块来探索在 Node.js 中运行的力量,然后我们通过 REPL 将所有这些结合起来!
问题
-
PWM 代表什么,它与 LED 有什么作用?
-
树莓派有哪些支持 PWM 功能的引脚?有多少个?
-
为什么我们需要一个 GPIO 扩展板来控制我们的 RGB LED?
-
不使用 PWM,我们的 RGB LED 能显示多少种颜色?
-
我们 GPIO 扩展器使用什么协议与树莓派通信?
-
颜色模块为我们做了什么?
-
REPL 如何帮助调试?是什么让它如此强大?
进一步阅读
-
关于 PWM 的更多阅读:
learn.sparkfun.com/tutorials/pulse-width-modulation
-
关于 I²C 的更多阅读:
learn.sparkfun.com/tutorials/i2c
第四章:使用按钮输入
我们现在已经在 Johnny-Five 中探讨了数字和 PWM 输出,但这只是故事的一半。在机器人项目中使用输入设备可以做很多事情,允许用户输入或观察项目周围的世界来影响输出。
我们将从用户输入设备——按钮开始。我们还将讨论树莓派如何处理数字输入,并将按钮构建到我们之前的项目中,允许用户停止彩虹颜色循环,并自行推进颜色。
本章将涵盖以下主题:
-
在机器人项目中使用输入
-
Johnny-Five 传感器和按钮对象
-
连接按钮
-
将按钮添加到我们的 RGB LED 项目中
技术要求
你需要你的树莓派,以及从第三章,“使用 RGB LED 构建交互式项目”项目中连接好的 RGB LED,以及 GPIO 扩展板。
在机器人项目中使用输入
你可以在机器人项目中使用输出设备做很多事情,但当你添加输入时,可能性变得无穷无尽。无论是用户控制的输入,如按钮和电位计,还是测量环境光线或空气质量等环境传感器的输入,输入设备可以为任何机器人项目增添新的维度。
数字输入与模拟输入的比较
就像数字和 PWM 输出一样,有两种类型的输入设备:数字和模拟。数字输入要么开启要么关闭:按钮是这种类型的典型例子。模拟输入根据它们所感应的内容提供不同级别的电压或信号;例如,光敏电阻在环境光线高时输出较高的电压信号,而在光线暗时输出较低的电压信号。
为了从模拟设备读取数据,你需要一个可以接受模拟输入的引脚。但正如我们在上一章中看到的,树莓派上的所有 GPIO 引脚都是数字的。幸运的是,有方法可以绕过这个限制。
如何使用树莓派处理模拟输入
在树莓派上获取模拟传感器数据有两种方法:添加具有模拟引脚的 GPIO 扩展器,或使用利用数字信号来通信模拟数据的传感器。
模拟 GPIO 扩展器
这些板几乎与我们在第三章,“使用 RGB LED 构建交互式项目”中使用的 GPIO 扩展板完全一样,除了它们不是添加 PWM 输出引脚,而是添加模拟输入引脚。这些板通常还使用 I²C 接口与树莓派通信。然而,我通常发现这些板是不必要的,因为许多收集多个通道数据的传感器(如加速度计)已经使用 I²C 或其他数字接口,而收集单个通道数据的少数传感器可以在板上的这些数字接口中找到。
使用具有数字接口的输入设备
这是我们项目中的做法。这些设备使用 UART、SPI 和 I²C 等协议,允许只有数字 GPIO 的设备接收模拟数据。在每个项目的材料中,所包含的设备不需要模拟输入引脚。
Johnny-Five 如何处理输入
因此,我们已经通过板ready
事件了解了 Johnny-Five 如何使用事件。如果你曾经用 C 和 Arduino 编程,你可能熟悉事件循环风格的程序——一个循环永远运行并检查输入设备的状态,然后相应地做出反应。你可能也了解中断驱动的编程,其中硬件引脚的变化会导致代码跳转到特定函数。
Johnny-Five 的代码更接近中断风格;事件驱动几乎所有的 Johnny-Five 项目。这有几个好处;你可以通过事件类型来组织你的代码,并确保每个功能只在需要时触发,无需处理自己的硬件中断例程。
当 Johnny-Five 项目从传感器或设备接收输入时,它会触发一个data
事件。但如果你只想在环境变化时运行函数怎么办?change
事件就是为你准备的。我们将在稍后的章节中更详细地探讨确切的事件类型和它们何时触发,但在此期间请记住,事件是捕捉你的传感器和输入设备数据的方式。
典型 Johnny-Five 项目的结构
Johnny-Five 项目由几个关键部分和构建块组成,这使得阅读示例变得非常容易。让我们通过一个示例来了解更多。
开始——包括库和创建我们的板对象
本节通过引入 Johnny-Five 为我们搭建了舞台。以下代码片段告诉它我们正在使用树莓派,并构建相应的板对象。如果你正在使用其他npm
模块,例如我们在第三章中使用的color
模块,你将使用require
将它们也引入这里,如下所示:
const five = require("johnny-five")
const Raspi = require("raspi-io")
let board = new five.board({
io: new Raspi()
})
板就绪事件处理器
在 Johnny-Five 项目中,除了头文件之外的所有其他内容都放在这个事件处理器中。这个处理器,如这里所示,意味着我们的板已准备好读取和写入 GPIO 引脚,并且任何在事件处理器外部运行的、操作 GPIO 的代码可能无法保证工作,并可能导致异常行为:
board.on("ready", function() {
// Everything else goes in here!
});
构建我们的组件对象
在板就绪事件处理器内部,我做的第一件事是为我项目的所有组件设置 Johnny-Five 对象。如果所有组件类型和引脚都在代码的同一位置,那么从代码中连接项目会更容易:
// remember, this goes inside the board ready event handler!
let LED = new five.LED('P1-7')
let button = new.five.Button('P1-8')
输入事件处理器和输出设备操作
这就是有趣的部分,我们等待输入并相应地操作输出!这将监视连接到P1–8
的按钮被按下,然后点亮一个 LED。但是,当按钮释放时,我们如何关闭 LED?为此,我们将再次查看 Johnny-Five 文档:
// We'll go over more about the Button in the next section!
// This is still inside the board ready event handler!
button.on('press', () => {
led.on()
})
Johnny-Five 按钮对象
在我们编程按钮项目之前,让我们仔细看看 Johnny-Five 按钮对象,以便我们知道要寻找哪些事件,以及构造函数需要我们从我们这里得到哪些信息。
按钮对象
当我们查看按钮的参数部分时,只有一个必需的参数,即引脚(pin)。因此,我们需要记住将信号连接到哪个引脚,但除此之外,默认值将很好地为我们服务:
-
invert
:默认为 false,并反转上和下值。我们希望保持这个 false,因为我们焊接的按钮不需要反转。 -
isPullup
:告诉将上拉电阻连接到其 GPIO 引脚的板初始化此按钮时上拉已启用。我们将自己焊接电阻,所以这个可以保持默认的 false。 -
isPulldown
:与isPullup
类似,但带有下拉电阻。由于我们将自己焊接下拉电阻,所以请将其保留为 false。 -
holdtime
:这是按钮必须按下多少毫秒后才会触发保持事件。在这里,默认的 500 毫秒将足够好。
还有一个名为collection
的特殊部分,它详细说明了如何使用同一个对象控制多个按钮。这是一个有趣的设计,虽然我们不会在我们的双按钮项目中探索它,但一个很好的附加项目是将它重构为使用按钮的collection
对象。
按钮事件
按钮对象使用三个事件,并且每个事件都可以用于单个按钮对象的实例:
-
press
,down
:这些是相同的事件,当按钮被按下时它们会被触发 -
release
,up
:当按钮释放时,这些事件被触发 -
hold
:当按钮按下时间超过构造函数中holdtime
参数设置的阈值时,此事件被触发
如果你之前处理过硬件,你可能会担心按钮事件的噪声
;一个按钮按下或释放时触发多个事件,按钮未按下时释放事件,等等。Johnny-Five 已经将去抖动功能集成到按钮对象中,因此无需担心噪声按钮!
焊接按钮
我们将在上一章的项目中添加按钮,以便用户可以通过按按钮来改变 RGB LED 的工作方式。当你看一个按钮时,你会看到四个触须。虽然按钮有四个触须,但按钮只有两个输入/输出——一个电流入的地方,一个当按钮被按下时电流出的地方。这是因为按钮本质上控制着电流的流动。当按钮没有被按下时,触点没有连接,电流无法流动,而当按下时,导体桥接两侧,电流就可以流动。这就是我们将如何使用按钮作为输入设备:高信号表示按钮被按下,低信号表示没有被按下。
在面包板上放置按钮
拿起你的按钮,观察底部的金属触须。通常,两对触须向按钮内部弯曲。每边都有一个进出,每对具有相同弯曲方向的触须中都有一个。放置在面包板上时,请记住这一点,如下面的图片所示:
当你在面包板上放置按钮时,你想要确保按钮跨越面包板中间的凹槽,以防止短路。
一旦你将按钮放入面包板,确保它放置得很好,并且没有触须卷曲到自身而不是进入面包板插座。如果有,使用尖嘴钳将其拉直。
现在你已经将按钮放置在面包板上,是时候将其连接到你的 Pi 上了。
使用下拉电阻
所以问题是,我们如何将三根线连接到只有两个引脚的设备上?我们将使用所谓的下拉电阻将输入连接到未连接到电源的按钮一侧。当按钮被按下时,电流将通过电阻流入信号线,我们将使用 Pi 的数字输入引脚来检测这作为按钮按下。
要做到这一点,将按钮的一侧连接到面包板上的 5V 电源引脚。在另一侧,放置一个 10K 欧姆的电阻,将其连接到面包板的另一行,并在该行放置一根线,将其连接到面包板上的 GPIO 引脚。然后,在按钮的第二行,在电阻下方放置一根线,将其连接到面包板上的 GND 引脚。
电阻防止了当按钮被按下时 Pi 短路,这会导致你的 Pi 暂时停止工作,如果时间过长,将会造成不可修复的损坏。
短路,或短路,是指电路的电源和地之间没有负载(如我们的电阻)连接,这会导致很多问题。要了解更多信息,请查看本章的“进一步阅读”部分,或任何电子学入门书籍。
添加按钮到我们的 RGB LED 项目
现在我们已经了解了按钮的工作原理以及如何将其连接到 Pi,让我们将两个按钮添加到我们的 RGB LED 项目中。
连接所有线路
在我们连接按钮之前,我们需要对我们的当前布线设置做一些整理。
使用电源和地线侧轨
从现在开始,我们将需要更多访问电源和 GND 引脚,我们不希望有很多很长的线在我们的项目中交错。所以我们将首先进行一些硬件重构。
-
从排线中取出 RGB LED 的地线。
-
从 GPIO 扩展器的 5V 和 GND 排线中取出 VCC 和 GND。
-
在排线的 5V 排和外部长排(如果有标记为红色和蓝色的,使用红色)之间放置一根线。
-
在排线和另一个外排之间放置一根线。
-
将 RGB LED 的地线插入到连接到排线 GND 的侧轨。
-
如下图所示,将 GPIO 扩展器的 GND 插入到连接到排线 GND 的侧轨,将 VCC 插入到连接到排线 5V 的侧轨。
连接按钮
现在我们已经整理好了电源和地线,让我们放置按钮。对于这两个按钮:
-
按照上一节所述,将按钮放置在面包板上,连接面包板中心的间隙。
-
将按钮的一侧连接到连接到排线 5V 引脚的侧轨。
-
在另一侧放置一个 10K 欧姆电阻,连接到一个空轨。
-
将带有电阻的按钮侧连接到连接到排线 GND 的侧轨。确保电阻在按钮和接地连接之间!
-
将电阻的另一端连接到排线上的一个引脚;如下一图所示,使用#5 为按钮 1,#6 为按钮 2:
现在你就可以编写一些代码了!
按钮 1 – 停止和启动彩虹
在我们设置按钮之前,我们将对彩虹循环程序进行一些重构,以适应按钮的新功能。
重构彩虹循环
我们将重构彩虹循环以执行以下操作:
-
查看作用域变量,以查看颜色是否应该在定时间隔内继续改变(用于停止和启动按钮)
-
将改变 RGB LED 到下一个颜色的代码拆分成自己的函数(用于下一个颜色按钮)
让我们看看重构:
board.on('ready', () => {
let rgbLED = new five.Led.RGB({
controller: "PCA9685",
pins: {
red: 0,
green: 1,
blue: 2
}
})
let colorCycle = true
setInterval(() => {
if( colorCycle ) {
colorChange()
}
}, 1000)
function colorChange() {
console.log(currentColor)
currentColor = color(colors[colorIndex])
rgbLED.color([currentColor.red(), currentColor.green(), currentColor.blue()])
colorIndex++
if(colorIndex >= colors.length) {
colorIndex = 0
}
}
})
我们将使button1
停止和启动彩虹颜色的循环。为此,我们需要做的是:
-
构建一个按钮对象来在代码中表示我们的按钮
-
监听 Johnny-Five 按钮对象 API 的
press
事件 -
添加一个名为
cycleOn
的可变变量,可以设置为 true 或 false,并让改变颜色的循环使用它来决定是否改变颜色 -
我们还将把改变颜色的逻辑提取出来,为下一个按钮做准备
让我们将其添加到我们的板子准备处理器的开头:
let button1 = new five.Button('P1-29')
button1.on('press', () => {
colorCycle = !colorCycle
})
在你的 Pi 上加载这个程序,使用sudo node rainbow-pause-button.js
运行它,看看按按钮几次会发生什么!
按钮 2 – 下一个颜色
现在我们将添加第二个按钮和按下处理程序,以便在按下时第二个按钮将颜色前进到数组中的下一个颜色:
let button2 = new five.Button('P1-31')
button2.on('press', () => {
colorChange()
})
现在,当你按下第二个按钮时,LED 的颜色将前进到数组中的下一个颜色。
摘要
本章汇集了用户输入和输出——RGB LED。我们学习了如何在 Johnny-Five 中使用输入事件来操作输出设备,这是大多数 Johnny-Five 项目的核心,并学习了如何使用多个输入(按钮)来实现不同的效果。
问题
-
Johnny-Five 按钮对象有哪些事件可用?
-
树莓派可以使用模拟输入设备吗?
-
我们将如何使用 Pi 与传感器一起使用?
-
为什么
RGB.LED
对象没有事件?
进一步阅读
-
关于模拟输入引脚的更多信息:
learn.sparkfun.com/tutorials/analog-to-digital-conversion
-
关于上拉电阻的更多信息:
learn.sparkfun.com/tutorials/pull-up-resistors
第五章:使用光传感器创建夜灯
在本章中,我们将探讨即使没有 Pi 内置的模拟输入引脚,我们仍然可以使用 Johnny-Five 和 Raspberry Pi 使用模拟传感器的方法。我们将利用这些知识来构建一个夜灯,该夜灯根据房间内的环境光打开和关闭 LED。
本章将涵盖以下主题:
-
使用 Pi 的模拟传感器
-
环境光传感器
-
创建我们的夜灯
技术要求
对于这个项目,您需要一个任何颜色的普通 LED 和一个 TSL2561 光传感器,可在 Adafruit(www.adafruit.com/product/439
)和其他许多供应商处购买。
本章的代码可在github.com/PacktPublishing/Hands-On-Robotics-with-JavaScript/tree/master/Chapter05
找到。
使用 Pi 的模拟传感器
我们在第三章中讨论了 Pi 上多路 PWM 输出引脚的缺乏,构建**交互式 RGB LED 项目,但我们尚未完全解决的问题是与输入相关的问题。数字输入,如按钮和开关,对于 Pi 来说很简单,任何数字输出引脚也可以用作数字输入引脚。但对于需要超过两种状态的事物,比如检测光、温度、湿度、距离或其他我们想要测量的任何事物,怎么办呢?答案在于使用多年来开发的专用通信协议,这些协议允许数字引脚通信模拟信息。
为您的 Pi 项目寻找合适的传感器
当您在寻找 Raspberry Pi 项目的传感器时,您需要确保您使用的任何模拟传感器都有数字接口。最常见的是 I²C 和 SPI,我们将讨论如何判断您的传感器是否有(或没有!)以及该设备是否可以与 Johnny-Five 一起使用。
I2C 设备
I²C 设备需要两个额外的引脚才能与电源和地线引脚一起工作——一个 SDA(数据)引脚和一个 SCL(时钟)引脚。这些引脚如何使用的细节超出了本书的范围(有关更多信息,请参阅进一步阅读部分),但要知道,只要设备有不同的 I²C 地址,您就可以将多个设备连接到相同的 SDA 和 SCL 引脚。地址是一个两位十六进制数,对于几乎所有 I²C 设备来说都很容易找到,在某些情况下,地址可以在设备上物理配置。
对于本章的项目,我们将使用 TSL2561,它可以配置两个不同的地址。现在我们将使用默认的 0x39(在 Adafruit 型号上)。
SPI
SPI 设备很快就会变得复杂,您需要五个引脚:电源、地、微控制器到传感器数据(MOSI)、传感器到微控制器数据(MISO)和芯片选择线。虽然一组 SPI 引脚上的多个设备可以共享 MISO 和 MOSI 引脚,但每个设备都需要自己的芯片选择引脚,这样微控制器就可以向它想要通信的设备发出信号。
如何确定您的传感器是否与 Johnny-Five 兼容
要查看您在 Johnny-Five 中关注的传感器是否已有驱动程序,最好的方法是检查 Johnny-Five 网站的文档。找到传感器类型,并了解传感器使用的是哪种芯片(例如,我们的光传感器使用的是 TSL2561)。然后,在传感器的 API 页面顶部,几乎总是有一个支持的控制器和芯片列表。如果您的传感器上的芯片编号与列表中的某个编号匹配,则它已经与 Johnny-Five 兼容:只需记住,尽管模拟传感器与 Johnny-Five 兼容,但它们不会与 Pi 一起工作,因为 Pi 没有自己的模拟输入引脚。
例如,以下是支持的光传感器控制器和芯片列表,您可以在列表中看到 TSL2561,所以我们准备好开始构建我们的项目:
环境光传感器
要开始我们的夜灯项目,我们将首先连接我们的 TSL2561 I²C 光传感器,并确保通过将其打印到命令行来获取良好的数据读取。
连接传感器
为了连接我们的光传感器,我们需要知道 Pi 的 SDA 和 SCL 引脚是哪些。对于 Pi 3 和 3 B+,SDA 是 P1-P3,SCL 是 P1-P5;这些通常也标有 SDA 和 SCL。为了使传感器工作,我们需要电源引脚;这个传感器不耐受 5V,所以我们需要使用 3.3V 电源引脚。我们可以将传感器的 GND 连接到任何地线引脚。
传感器上的 SDA 和 SCL 引脚需要分别连接到 Pi 上的 SDA 和 SCL 引脚。最后,您的光传感器应该按照以下图示连接:
现在我们已经连接了传感器,是时候考虑如何使用 Johnny-Five 和其他 Node.js 模块打印数据,以确保它正在运行。
编写程序读取数据并将其打印到命令行
Johnny-Five 中的传感器对象事件与按钮事件不同,因为,嗯,传感器与按钮不同!让我们看看它们之间的差异以及如何从我们的光传感器获取所需的数据。
Johnny-Five 传感器事件
我们将从传感器看到的两个主要事件是 data
和 change
。唯一的真正区别在于名称:data
事件在每次检索数据时都会触发,而 change
事件在数据变化时触发。我倾向于在构建基于传感器的项目时使用 change
,除非我明确地需要记录随时间变化的数据。
你可以在传感器对象的构造函数中配置数据收集的时间间隔,以及数据变化必须通过的阈值才能触发change
事件。
在事件处理器中处理传感器数据
当你从传感器接收数据时,它将被附加到 JavaScript 的this
对象上,因此当你为事件处理器创建回调时,不要使用箭头语法,因为这样你会失去 Johnny-Five 在 JavaScript 的this
对象上设置的绑定。
这里是一个 Johnny-Five 传感器上change
事件的通用数据处理器示例:
let mySensor = new five.Sensor('PIN_NUMBER')
mySensor.on('change', function() {
console.log(this.value) // logs a value between 0-255 to the console
})
既然我们已经确定了如何获取数据,让我们谈谈数据将是什么样子以及我们如何可以操作它。
使用和格式化 Johnny-Five 传感器数据
在 Johnny-Five 中接收传感器发送的数据有很多种方法,正如以下截图所示的文档所示:
布尔值、原始值、模拟值、约束值和值可能会让你有很多要处理的数据。每个值的意义在前面的图中都有展示,然而请注意,默认值与模拟值相同的原因:一个介于0
和255
之间的缩放读取值。这与可用的传感器种类、数据的粒度变化以及使用缩放以确保你只需要记住一个数字范围有很大关系,无论你使用多少个传感器。
使用.scaleTo()和.fscaleTo()来微调测量值
如果你想要对你的传感器施加一个任意的比例(比如0
– 100
表示百分比),Johnny-Five API 中内置了一些选项:.scaleTo()
和.fscaleTo()
。这些选项会将传感器的原始值缩放到你传入的最小值和最大值:
sensor.on('change', function(){
// this.value will reflect a scaling from 0-1023 to 0-100 console.log(this.scaleTo([0, 100])); // prints an integer
console.log(this.fscaleTo([0, 100])); // prints a float
})
既然我们已经知道如何处理数据,让我们开始创建代码来将光传感器值打印到命令行。这将允许我们调整变化阈值设置,并确定我们应该使用哪个光传感器值作为指示器来打开和关闭我们的 LED。
将传感器数据打印到命令行
要将传感器数据打印到命令行,我们将使用print-light-sensor.js
中的代码:
const five = require('johnny-five')
const RaspiIO = require('raspi-io')
let board = new five.Board({
io: new RaspiIO()
})
board.on('ready', () => {
let lightSensor = new five.Light({
controller: 'TSL2561'
})
lightSensor.on('change', function() {
console.log(this.value)
})
})
运行时的输出应该看起来像这样,数字在覆盖或照射传感器时会有所变化:
这很好,但有点难以理解。接下来,我们将添加npm
模块barcli
来显示一个漂亮的条形图,使我们能够实时理解我们看到的数据。
使用 barcli 使数据更容易观察
那个数据流可能很难处理!让我们看看如何利用 Node.js 的强大功能使这个过程更容易观察。
在你的project
文件夹中运行:
npm i --save barcli
要安装barcli
,这是一个在终端中创建条形图的库。
阅读关于barcli
的文档(见进一步阅读),我们需要导入barcli
,使用我们需要的设置构造一个barcli
对象,然后告诉何时更新以及使用什么数据。
导入 barcli 并构建我们的 barcli 图表
要导入barcli
,在你的print-light-sensor.js
文件顶部,在其他的require()
语句之后,添加:
const Barcli = require('barcli')
然后,在board.on('ready')
处理程序中,我们将添加条形图构造函数:
let lightGraph = new Barcli({
label: 'Light Sensor',
range: [0, 255]
})
获取条形图更新
从lightSensor.on('change')
处理程序中删除console.log()
行,并用以下内容替换:
lightGraph.update(this.value)
然后你就可以开始了!将project
文件夹移动到 Pi 上,在 Pi 的 SSH 会话中导航到该文件夹,并运行:
npm i
为了确保barcli
在 Pi 上正确安装,运行以下命令:
sudo node light-sensor-barcli.js
你现在应该看到一个条形图,如下所示,当你在传感器上照射光线或遮住传感器时,它会改变:
现在,对于我们的夜灯项目,你需要找到一个用于控制 LED 开关的光传感器值;barcli
通过使该值更容易看到,使这个过程变得容易得多。
一旦你找到了适合你的值(我选择了25
),我们就准备好构建我们的夜灯了。
创建我们的夜灯
既然我们知道我们的光传感器工作正常,我们可以添加一个 LED 并创建我们的夜灯。
连接 LED
使用一个 330K 欧姆电阻将你的 LED 的短腿连接到地轨,并将长腿连接到 GPIO #5,也称为 P1-29:
编码这个项目
在与本章其他文件相同的文件夹中创建一个文件,并将print-light-sensor-readings.js
的内容复制到其中。
在board.on('ready')
处理程序的开始处,添加我们的 LED 构造函数:
let light = new five.Led('P1-29')
在lightSensor.on('change')
函数中,将console.log
语句替换为将 LED 开关的逻辑:
if(this.value <= 25) {
light.on()
} else {
light.off()
}
我们已经准备好了运行!将文件夹加载到你的 Pi 上,在 Pi 的 SSH 会话中导航到该文件夹,并运行:
sudo node night-light.js
当你用大拇指遮住光传感器时,LED 应该亮起,如下面的图片所示:
当你移开你的大拇指(在一个明亮的环境中),LED 将关闭,如下面的图片所示:
就这样,你已经成功编码并构建了你的夜灯!
摘要
在本章中,我们学习了关于模拟传感器和树莓派模拟输入的限制。我们学习了允许我们在树莓派项目中收集模拟数据的数字接口。我们使用这些知识来设置一个光传感器,使用barcli
的条形图来找到一个好的阈值,使 LED 开关。最后,我们将所有这些结合起来,构建了一个在黑暗中照亮并在光亮时关闭的夜灯。
问题
-
什么是模拟输入传感器?
-
为什么模拟输入传感器不能直接与树莓派接口?
-
列出两种我们可以与 Pi 一起使用来收集模拟数据的数字接口。
-
除了电源和地线之外,I²C 传感器需要哪两个引脚才能运行?
-
列出传感器对象可以触发的事件。
-
为什么
barcli
在处理变化中的传感器数据时很有帮助?
进一步阅读
-
关于模拟输入的更多信息:
learn.sparkfun.com/tutorials/analog-to-digital-conversion
-
关于 SPI 的更多信息:
learn.sparkfun.com/tutorials/serial-peripheral-interface-spi
-
关于 I²C 的更多信息:
learn.sparkfun.com/tutorials/i2c
-
关于在 Pi 上使用 SPI 和 I²C 的更多信息:
learn.sparkfun.com/tutorials/raspberry-pi-spi-and-i2c-tutorial
第六章:使用电机移动你的项目
我们已经探讨了使用输入来发现我们机器人周围的世界,以及使用输出让我们的机器人进行通信,但任何机器人都应该具备的另一项关键技能是:移动的能力!在接下来的几章中,我们将讨论各种让我们机器人移动的方法,并讨论如何控制这种移动。我们将从本章开始,讨论最简单的移动组件:电机。
本章将涵盖以下主题:
-
更多关于电机的内容
-
使用 Raspberry Pi 为电机驱动项目做准备
-
Johnny-Five 电机对象
-
解决你的电机化项目的故障
-
项目 - 构建一个随机电机化猫玩具
-
项目 - 使用齿轮箱电机和电机对象
技术要求
你需要 Adafruit Pi 电机帽子套件(www.adafruit.com/product/2348
),一个小型 5V 电机,你也可以从 Adafruit(www.adafruit.com/product/711
)或许多其他供应商那里购买,一个带有电线端子和开关的 4xAA 电池盒,可以从 Adafruit(www.adafruit.com/product/830
)和许多其他供应商那里购买,2 个齿轮箱或TT电机,可以从 Adafruit(www.adafruit.com/product/3777
)和许多其他供应商那里购买,以及一个贴纸(或一张纸,剪刀和胶带)。
注意:如果你不会焊接或者不习惯焊接,可以在亚马逊上找到一种替代的全组装帽子(www.amazon.com/SB-Motorshield-Raspberry-expansion-ultrasonic/dp/B01MQ2MZDV/ref=sr_1_fkmr1_1?s=electronics&ie=UTF8&qid=1534705033&sr=8-1-fkmr1&keywords=raspberry+pi+motor+controller+TB6612
)。我将在必要时注明代码中的更改——任何本章引用的 L293D 帽子,即指这个帽子。
本章的代码可在github.com/PacktPublishing/Hands-On-Robotics-with-JavaScript/tree/master/Chapter06
找到。
更多关于电机的内容
电机是一种可以以不同速度连续旋转轴的组件。然而,电机有很多不同的种类;让我们看看一些例子:
-
直流电机:这种电机是最简单的:它可以单向运行,速度由你提供的功率决定。这些通常只有两根线:一根接地,一根供电;我们将后者与电机帽子结合来控制速度。使用正确的控制器,我们可以使电机双向移动。
-
带有刹车的电机:这些电机有一个额外的线来控制刹车,可以在不需要减速到停止的情况下停止电机,就像直流电机一样。Johnny-Five 库支持这些电机,但本书不会涉及。
-
步进电机:步进电机用于精确运动,因为它们的运动是按电机尺寸变化的步进。它们设计为双向的,非常适合需要电机扭矩且精确的地方。我们将在本章的第二项目中更多地讨论这些内容。只需知道,一个简单的识别步进电机的方法是它有 5 根线,而不是 2 或 3 根:
左边是普通电机,右边是步进电机
如何使用微控制器控制电机
你可以直接将直流模型电机连接到微控制器的 PWM 引脚来为其供电,但这通常是不推荐的:电机消耗大量电力,许多微控制器限制每个引脚的电流输出。
更推荐的解决方案,我们将在本章中使用,是使用外部电机控制器;这些通常包含进行更复杂运动所需的电路(例如允许它们反向运动),并允许外部电源为电机提供必要的电力,而不会从微控制器中吸取电力。
准备使用树莓派进行电机驱动项目
为了使用 Johnny-Five 和树莓派开始学习电机,我们需要添加一个帽子(想象一下 Arduino 面板,但针对 Pi,或者如果你是电子新手,则是堆叠在 Pi 上的附加板)以使我们能够:
-
为电机提供外部电源
-
比树莓派本身控制电机更好(尤其是在步进电机的情况下)
组装帽子
按照如下方式将电池组和电机连接到帽子的螺丝端子:
图中的黄色线应该是你的地线(通常是黑色),绿色应该是你的电源线(通常是红色)。
将帽子安装在 Pi 上
从 Pi 上移除所有电源,并确保电池组已关闭。此外,如果它还连接在 GPIO 引脚上,请移除 Cobbler。然后,将帽子底部的插槽与 Pi 顶部的引脚对齐,使其位于 Pi 之上。然后,轻轻按下帽子直到它就位。不要用力按——你可能会弯曲一些 Pi 的引脚。一切就绪后,它应该看起来像这样:
电机应该这样插入螺丝端子:
重新为 Pi 上电,然后我们将开始使用 Johnny-Five 电机对象进行编码。
Johnny-Five 电机对象
Johnny-Five 中的电机对象允许我们轻松控制电机,而无需担心通过 Pi 与帽子通信。在我们编写项目之前,让我们用 REPL 编写一个测试设置,以确保一切正常。
创建一个新的 project
文件夹,并在其中运行以下命令:
npm init -y
然后,在文件夹中创建一个名为 motor-test.js
的文件。首先,引入 Johnny-Five 和 Raspi-IO,实例化你的板对象,并创建一个 board.on('ready')
处理器,就像我们通常做的那样:
const Raspi = require('raspi-io')
const five = require('johnny-five')
const board = new five.Board({
io: new Raspi()
})
board.on('ready', () => {
})
现在,我们已经准备好设置我们的电机对象,记住我们需要为我们的帽子进行配置。
我们帽子的构造函数
如果你使用的是 Adafruit 帽子,你的构造函数如下:
let motor = new five.Motor(five.Motor.SHIELD_CONFIGS.ADAFRUIT_V2.M1)
如果你使用的是 L293D 帽子,你的构造函数如下:
let motor = new five.Motor(five.Motor.SHIELD_CONFIGS.ADAFRUIT_V1.M1)
将适用的任何一个放入 board.on('ready')
函数中。
移动电机的函数
参考 Johnny-Five 文档,有几个函数允许我们通过命令行使用 REPL 移动电机:
motor.forward(speed) // speed 0-255, starts the motor forward
motor.stop() // the motor coasts to a stop
motor.start(speed) // resumes the motor moving forward
motor.reverse(speed) // moves the motor backward
现在我们知道了如何控制电机,让我们添加 REPL 功能来测试它
添加 REPL 控制
在 board.on('ready')
函数的末尾添加以下内容:
board.repl.inject({
motor
})
现在我们有了完全的控制权!
加载和运行你的电机
将项目加载到 Pi 上,并在 Pi 的 ssh 会话中导航到该文件夹。然后,运行以下命令:
npm i --save johnny-five raspi-io
完成之后,打开电池组并运行以下命令:
sudo node motor-test.js
一旦看到 Board Initialized,你应该尝试之前的一些命令:
motor.forward(speed) // speed 0-255
motor.stop()
motor.start(speed)
motor.reverse(speed)
希望你的电机正在愉快地旋转!
解决你的电机项目故障
但如果你的电机不转动怎么办?如果你的电机没有旋转,这里有几点需要检查:
- 电机帽子的电池组已经打开了吗?不要笑,我花了很多时间在怀疑为什么它不起作用,最后发现它没有电源。大多数电机帽子上都有一个电源指示灯,告诉你它是否有电:
Adafruit 帽子上的电源 LED 正在外部电源的螺丝端子上方,并亮起红色
-
你的电池是新的是吗?电机消耗的电量很大,长时间使用会很快耗尽它们。
-
正如我在第一章中提到的:检查你的接线。然后,再检查一遍。
-
确保所有电线都牢固地固定在正确的螺丝端子上,这样轻轻一拉就不会松动。
-
你在使用可充电电池吗?如果是这样,我钦佩你对再利用的承诺,但你需要 6 个可充电电池来运行电机,因为可充电电池和非可充电电池之间的电压不同。
希望你的电机之前没有旋转,现在应该已经旋转了,我们可以构建我们的第一个项目。
项目 – 猫玩具
在这个项目中,我们将在电机上放一张纸,然后编写一些随机代码,使其以不同的速度来回旋转(毕竟,猫对可预测的玩具会感到无聊)。
这个项目的布线与电机测试相同;那里不需要做任何改变。
在电机轴上放一张纸
要么用一条长粘性便条的粘性端绕着电机轴滚动,要么用一条长纸条粘在上面。它应该看起来像这样:
在我们玩具相对简单的构造之后,让我们编写一些随机的代码!
编写代码以随机启动/停止电机
我们希望电机以 1-10 秒内的任意速度启动,然后停止 1-10 秒,然后重复。我们还想让它向前或向后移动是随机的。我把速度限制在 75——任何更快的东西对我来说猫都承受不了!
在你的cat-toy.js
文件中,删除board.repl.inject
语句,并添加以下内容:
startMovement()
function startMovement(){
let direction = Math.round(Math.random())
let speed = Math.round(Math.random() * 75)
let time = Math.round(Math.random() * 10)
if(direction == 0){
motor.forward(speed)
} else {
motor.reverse(speed)
}
setTimeout(stopMovement, time*1000)
}
function stopMovement(){
let time = Math.round(Math.random() * 10)
motor.stop()
setTimeout(startMovement, time * 1000)
}
这将随机化启动和停止、速度和方向。我的猫至少被它轻微地娱乐了。如果你有猫,不妨试试!
将项目加载到 Pi 上,在 Pi SSH 会话中导航到该文件夹。然后运行:
sudo node cat-toy.js
然后看看它怎么走!
我们已经有一个电机在工作,但如果你想要制作一个带轮子的机器人,我们需要两个电机;让我们在下一个项目中看看这个概念。
项目 – 使用两个齿轮电机和电机对象
现在我们已经探索了电机对象,让我们深入一点,在探索电机对象的同时,使用两个 TT 电机构建一个项目。
如果你想要更进一步,你可以从 Adafruit 购买这样的底盘www.adafruit.com/product/3796
和一对这样的轮子www.adafruit.com/product/3757
,然后自己组装一个移动的 2 轮机器人!记住,你可能需要用电池组给 Pi 供电(那些为手机充电的小 USB 包效果很好)或者保持在 Pi 电源线的范围内。如果你选择后者,我会把电源插头固定在 Pi 上,并且非常小心不要让机器人拉得太用力。说实话,如果要让 Pi 自己移动,我强烈建议使用电池。
连接你的 TT 电机
对于这个图,假设正常的直流电机是我们的 TT 电机——黄色将是地线(通常是黑色)线,绿色将是电源线(通常是红色)。
现在是时候开始编写代码,让我们的电机使用 Johnny-Five 和电机对象执行常见的轮式车辆运动了。
Johnny-Five 的电机对象
在你的项目文件夹中创建一个名为driver-bot.js
的新文件。从 Johnny-Five 和 Raspi-IO 库的常规设置开始,你的板对象,以及你的board.on('ready')
处理程序:
const Raspi = require('raspi-io')
const five = require('johnny-five')
const board = new five.Board({
io: new Raspi()
})
board.on('ready', () => {
})
接下来,在board.on('ready')
处理程序中,我们将添加两个 TT 电机的构造函数:
如果你使用的是 Adafruit 帽子,你的构造函数是:
let leftMotor = new five.Motor(five.Motor.SHIELD_CONFIGS.ADAFRUIT_V2.M1)
let rightMotor = new five.Motor(five.Motor.SHIELD_CONFIGS.ADAFRUIT_V2.M2)
如果你使用的是 L293D 帽子,你的构造函数是:
let leftMotor = new five.Motor(five.Motor.SHIELD_CONFIGS.ADAFRUIT_V1.M1)
let rightMotor = new five.Motor(five.Motor.SHIELD_CONFIGS.ADAFRUIT_V1.M2)
现在我们已经构建了电机,我们将通过传递包含leftMotor
和rightMotor
的数组来创建 Motors 对象:
let motors = new Five.Motors([leftMotor, rightMotor])
在我们开始编写驾驶函数之前,让我们谈谈 Motors 对象的好处。将你的电机放在 Motors 对象中的主要好处是,在同时能够控制所有电机的同时,还能保持对每个单独电机的控制。例如:
leftMotor.forward(255) // left motor full speed ahead!
rightMotor.reverse(255) // right motor full speed reverse!
motors.stop() // both motors coast to a stop
Motors 对象允许你调用任何 Motor 对象函数,并且它将一次性在所有电机上执行。
让我们利用这些知识来编写一些常见的驾驶函数,我们可以用这些函数与我们的电机一起使用。
编写一些函数
首先,我们希望让我们的机器人向前行驶。在driver-bot.js
的board.on('ready')
处理程序中,添加以下内容:
function goForward(speed) {
motors.forward(speed)
}
在这里,我们再次看到了motors
对象的好处;我们不需要分别告诉左右电机向前移动。
让我们添加另一个函数,让我们的电机滑行到停止:
function stop() {
motors.stop()
}
另一个让我们的机器人向后行驶:
function goBackward(speed) {
motors.reverse(speed)
}
现在已经完成这些,我们再添加一些转弯如何?幸运的是,motors
对象仍然允许我们单独控制每个电机——所以转弯不是问题:
function turnRight(speed) {
leftMotor.forward(speed)
rightMotor.stop()
}
function turnLeft(speed) {
rightMotor.forward(speed)
leftMotor.stop()
}
最后,让我们添加原地左转或右转的能力:
function spinRight(speed) {
leftMotor.forward(speed)
rightMotor.reverse(speed)
}
function spinLeft(speed) {
rightMotor.forward(speed)
leftMotor.reverse(speed)
}
现在,我们的电机拥有了它们驱动所需的全部!让我们给自己提供 REPL 访问这些方法、motors
对象和motor
对象:
board.repl.inject({
leftMotor,
rightMotor,
motors,
goForward,
goBackward,
stop,
turnRight,
turnLeft,
spinRight,
spinLeft
})
现在我们已经准备好加载我们的代码,并让我们的电机转起来(既指比喻意义也指字面意义)!
运行我们的电机项目
将项目加载到 Pi 上,并在 Pi SSH 会话中导航到该文件夹。然后运行:
sudo node driver-bot.js
一旦你看到Board Initialized,你可以自由尝试我们的新函数:
goForward(100) // start moving both motors forward
stop() // and stop
goBackward(50) // go backward at half the previous speed
stop()
goForward(100)
turnRight(100)
turnLeft(200) // a faster left turn
spinRight(255) // robots can't get dizzy, maximum fastness
spinLeft(100)
stop()
就这样——你已经编写了你需要驱动两轮机器人的所有代码!
作为附加项目,想想你如何能够驱动机器人而不必每次都输入函数名!
摘要
在本章中,我们学习了为我们的机器人添加运动的第一组件:电机。我们学习了电机的类型以及如何将它们与微控制器接口连接。然后,我们编写了代码来测试我们的电机与 Pi 帽和 REPL,并利用我们对 Johnny-Five Motor 对象的了解制作了一个小型随机猫玩具。最后,我们构建了一个项目,使我们能够亲手探索 Motors 对象的能力,并编写代码来驱动两轮机器人。
问题
-
电机是什么?
-
电机和步进电机有什么区别?
-
为什么你应该为电机使用外部电源?
-
为什么我们需要一个帽子来控制我们的电机?
-
使用多个电机时,Motors 对象有哪些好处?
第七章:使用伺服电机进行测量运动
现在我们已经了解了电机,让我们看看一种更精确的方法来为我们的项目添加运动:伺服电机。我们将深入了解如何连接一个伺服电机,然后是另一个,以及如何编写单个伺服电机和多个伺服电机的代码。
本章将涵盖以下主题:
-
电机和伺服电机的区别
-
使用 Johnny-Five 使伺服电机工作
-
项目 – 两个伺服电机和 REPL
-
项目 – 连续伺服电机
技术要求
你需要你的 Pi、Cobbler、PWM 帽、2 个业余伺服电机,以及来自第六章,使用电机移动你的项目的 AA 电池组。你还需要一个连续伺服电机。最后,你需要你的光传感器。可选但很有帮助的是一根冰棍或其他小棍和一些胶带,将我们的伺服电机变成一个仪表。
本章的代码可在以下位置找到:github.com/PacktPublishing/Hands-On-Robotics-with-JavaScript/tree/master/Chapter07
。
电机和伺服电机的区别
平均的业余伺服电机看起来像这样:
同时展示的还有常见的配件:一些不同的臂和安装螺丝/垫圈。电线端头终止于一个坚固的三孔插座:非常适合连接到我们的 PWM 帽。
我们在上一章中使用的电机有一些非常基本的不同之处,在我们开始伺服电机项目之前,我们应该探索它们。
计算后的运动
与我们在上一章中处理的电机不同,你可以使用伺服电机进行精确和计算后的运动。在常规伺服电机上命名一个介于 0 到 180 度之间的角度,它就会移动。电机(不包括本书中未涉及的步进电机)无法进行这些精确的运动。所以,如果你想要让轮子移动,而且你不在乎精确的运动,可以使用电机。当你想要让需要与其他关节精确移动的肢体关节移动时,是时候使用伺服电机了。
常规伺服电机与连续伺服电机的区别
有两种伺服电机,它们看起来非常相似。另一种被称为连续伺服电机。它们的工作方式与 Johnny-Five 中的常规伺服电机非常相似,正如你在servo.continuous的文档中可以看到的,该文档与伺服电机文档在同一页上。主要区别在于,常规伺服电机只能旋转 180 度,而连续伺服电机可以旋转满 360 度,并且可以无限期地继续在同一方向上旋转。有些人喜欢使用这些电机来移动他们的车辆项目中的轮子,这完全没问题!
为伺服电机和电机供电
这就是伺服电机和电机非常相似的地方:虽然一个伺服电机通常比电机消耗的功率要少,但许多项目使用的伺服电机比电机要多,这可能会对你的树莓派造成压力。如果你添加了很多伺服电机,你将需要为 PWM 帽提供外部电源;许多人使用 3-4 节 AA 电池组。我建议使用我们在第六章,使用电机移动你的项目中使用的电机电源。
从树莓派供电你的伺服电机可能会有你意想不到的后果--如果你在运行伺服电机代码时遇到内存泄漏问题,那可能是因为你从树莓派中拉取了过多的电源!
通常,如果你在运行伺服电机代码时遇到奇怪的问题,请确保你的伺服电机由外部电源充足供电。
让伺服电机与 Johnny-Five 一起工作
要让伺服电机与 Johnny-Five 一起工作,我们将查看 Johnny-Five 伺服对象,讨论如何将伺服电机连接到我们的 PWM 帽,并编写我们的第一段代码,让伺服电机来回摆动。
Johnny-Five 伺服对象
在 Johnny-Five 文档的 API 部分查看伺服电机页面,我们首先会查找构造函数。因为我们仍在使用 PCA9685 PWM 帽,所以我们的构造函数将看起来像这样:
let servo = new five.Servo({
controller: "PCA9685",
pin: 0
});
至于移动伺服电机,文档中描述了几种移动伺服电机的方法。第一种移动可以是到一个固定位置:
servo.to(degree)
servo.min()
servo.max()
servo.home()
servo.center()
或者,另一种方法是来回摆动,要么尽可能远地来回摆动,要么在某个范围内:
servo.sweep() // goes 0-180 and back, then repeats
servo.sweep(minDegree, maxDegree) // goes min to max and back, then repeats
你也可以停止一个正在移动的伺服电机:
servo.stop()
现在我们已经很好地掌握了在 Johnny-Five 中编写伺服电机代码,让我们连接一个伺服电机并测试我们的新知识。
将伺服电机连接到我们的 PWM 帽
将伺服电机连接到 PWM 帽相对简单;你需要将伺服电机的 3 针插座对齐到你想用的引脚列。你还需要将屏蔽罩的电源线连接到第六章,使用电机移动你的项目中的 AA 电池组。
我发现找出哪个引脚是地线,然后确保该插座位于底部,有助于解决这个问题。伺服电机上的地线通常是黑色或棕色。然后,将插座对齐并滑动到第一列的引脚上(引脚 0)。
我们已经连接了伺服电机并为其 PWM 帽供电,所以让我们编写一段代码来控制伺服电机。
编写你的第一个伺服电机摆动代码
在一个名为single-servo.js
的文件中,我们将设置我们的板,然后是我们的伺服电机,当它准备好时,告诉它来回摆动:
const Raspi = require('raspi-io')
const five = require('johnny-five')
const board = new five.Board({
io: new Raspi()
})
board.on('ready', () => {
let servo = new five.Servo({
controller: "PCA9685",
pin: 0
})
servo.sweep()
})
将文件移动到树莓派上的单独文件夹中,在 Pi SSH 会话中导航到该文件夹,并运行以下命令:
npm init -y
npm i --save johnny-five raspi-io
sudo node single-servo.js
你应该看到伺服电机来回移动。太棒了!现在,是时候使用 Johnny-Five 伺服对象和命令行 REPL 来控制两个伺服电机了。
项目 – 两个伺服电机和 REPL
现在我们有一个伺服正在运行,我们将连接第二个伺服,并使用 REPL 来探索 Johnny-Five 伺服对象,该对象旨在帮助同时控制多个伺服。
首先,让我们连接第二个伺服。
连接第二个伺服
拿起第二个伺服,找出哪一边是地线,将那个放在下面,然后将三针插座滑过第二列(引脚 1)的引脚:
现在我们已经连接了第二个伺服,让我们开始编写我们的 Johnny-Five 伺服对象代码!
使用 Johnny-Five 伺服对象
Johnny-Five 伺服对象旨在帮助你在项目中以有意义的方式分组伺服,例如六足机器人,每个都包含多个伺服。
你可以通过几种不同的方式创建一个 Servos
对象;我们将使用的方法是传递一个由构造的 servo
对象组成的数组:
let servos = new five.Servos([servoOne, servoTwo])
这就是魔法发生的地方——现在我们的 servo
对象被分组在 Servos
对象中,我们可以独立地控制它们,也可以作为一个组来控制:
servoOne.to(0) // Sets servoOne to 0 degrees
servoTwo.to(180) // Sets servoTwo to 180 degrees
servos.center() // Sets both servos to 90 degrees
并且 所有伺服对象函数都可在 Servos 对象上使用。
让我们把这段代码添加到我们的代码中。
将 Servos 对象添加到我们的代码中
在与 single-servo.js
相同的文件夹中创建一个新文件,命名为 servos-repl.js
,并将 single-servo.js
的内容复制到其中。
然后,在 board.on('ready')
处理程序中,将 servo
重命名为 servoOne
,并在 PWM 帽的 1
号引脚上添加 servoTwo
的构造函数:
let servoOne = new five.Servo({
controller: "PCA9685",
pin: 0
})
let servoTwo = new five.Servo({
controller: "PCA9685",
pin: 1
})
然后,我们将构造一个包含 servoOne
和 servoTwo
的 Servos
对象:
let servos = new five.Servos([servoOne, servoTwo])
现在我们已经编写了伺服的代码,让我们添加 Johnny-Five REPL 功能,这样我们就可以从命令行控制我们的伺服。
添加 REPL 功能
首先,从 servos-repl.js
中删除 servo.sweep()
行,因为它现在会导致错误。相反,将以下代码放在 Pi 上,这将允许我们从命令行访问我们的两个伺服和伺服组对象:
board.repl.inject({
servoOne,
servoTwo,
servos
})
现在我们准备好玩我们的伺服了!
在命令行中玩我们的伺服
将文件夹加载到 Pi 上,在 Pi SSH 会话中导航到该文件夹,并运行以下命令:
sudo node servos-repl.js
一旦看到 Board Initialized
,REPL 就准备好接受命令了。先试试这些:
servoOne.to(0)
servoTwo.to(180)
servos.center()
使用 Johnny-Five 的伺服文档查看您可以从命令行尝试的其他有趣的事情!
项目 - 使用伺服的光量计
让我们构建一个项目,其中我们的伺服作为光量计,根据光传感器的读数在 0 到 180 度之间扫描。
添加光传感器
首先,我们需要将光传感器连接到板上。记住,只要 I²C 设备有不同的地址(TSL2591 在 0x29,PWM 帽在 0x40),它们就可以共享一个 SDA 和 SCL 引脚。
现在我们已经连接了传感器,我们将承担(可选的)任务,修改我们的伺服,使其看起来更像一个仪表。
将伺服变成仪表
拿起伺服机构臂,将臂的中心朝向你移动,尽可能向左移动(0 度)。不要用力过猛,否则你会损坏齿轮. 然后,用胶带将棍子粘到伺服机构臂上,使其看起来更长。然后,你可以将其粘到桌子上或墙上,仪表指向左边。你可以在下面的图中看到我的尝试:
现在我们已经将光传感器连接好,并将仪表组合在一起,让我们编写我们的光量表代码。
编码项目
在你的project
文件夹中创建一个名为light-meter.js
的新文件。设置你的正常脚手架:引入 Johnny-Five 和 Raspi-IO,设置Board
对象,并创建board.on('ready')
处理程序:
const Raspi = require('raspi-io')
const five = require('johnny-five')
const board = new five.Board({
io: new Raspi()
})
board.on('ready', () => {
})
在board.on('ready')
处理程序内部,构建你的Servo
和Light
对象:
let servo = new five.Servo({
controller: "PCA9685",
pin: 0
})
let lightSensor = new five.Light({
controller: 'TLS2561'
})
然后,我们需要构建一个lightSensor.on('change')
处理程序。我们将使用Sensor.scaleTo([min, max])
将光传感器的 0-255 输入缩放到servo
的0
-180
输出:
lightSensor.on('change', function(){
servo.to(this.scaleTo([0, 180]))
})
就是这样!让我们把它放到树莓派上,看看它的运行情况。
运行和使用我们的光量表
将你的project
文件夹加载到树莓派上,在树莓派的 SSH 会话中导航到该文件夹,并运行以下命令:
sudo node light-sensor.js
然后,覆盖光传感器,或者向其照射光线——伺服机构应该相应地来回移动。
现在我们已经探讨了伺服机构作为数据通信手段以及创建运动的手段,让我们来看看连续伺服机构。
项目 – 连续伺服机构
连续伺服机构有点像电动机与普通伺服机构的结合:你失去了像普通伺服机构那样到达特定角度的能力,但你可以在瞬间停止,而不是像许多电动机那样滑行到停止。你可以告诉连续伺服机构以不同的速度顺时针或逆时针移动,你也可以让它停止。
让我们连接一个连续伺服机构,并通过 Johnny-Five REPL 来玩转它的功能。
连接伺服机构
连接方式上唯一的区别是连续伺服机构与普通伺服机构的外观不同,因为几乎所有连续伺服机构都有一个圆盘而不是臂。而且大多数都有红色、白色和黑色的电源、信号和地线,分别对应。
(Fritzing 没有连续伺服机构对象,所以我们将将就使用。)
连续伺服机构构造函数和方法
连续伺服机构的构造函数类似于 RGB LED 的构造函数,因为它属于Servo
对象的一个属性。否则,它看起来与我们的 PWM 帽上的Servo
构造函数非常相似:
let continuousServo = new five.Servo.Continuous({
controller: "PCA9685",
pin: 0
})
我们可以使用三种方法与连续伺服机构一起使用:
Servo.Continuous.cw([speed of 0-1]) // turns the servo clockwise
Servo.Continuous.ccw([speed of 0-1]) // turns the servo counter-clockwise
Servo.Continuous.stop() // stops the servo
现在我们知道了如何使用它,并且已经将其连接好,让我们编写一个快速程序,让我们可以在 REPL 中玩转我们的连续伺服机构。
使用连续伺服机构与 REPL 交互
在你的project
文件夹中创建一个名为continuous-servo-repl.js
的文件,并从你通常的设置开始:
const Raspi = require('raspi-io')
const five = require('johnny-five')
const board = new five.Board({
io: new Raspi()
})
board.on('ready', () => {
})
然后,在我们的board.on('ready')
处理程序中,构造Servo.Continuous
对象:
let continuousServo = new five.Servo.Continuous({
controller: "PCA9685",
pin: 0
})
最后,在构造函数之后,将连续伺服电机注入到 REPL 中,这样我们就可以使用它了:
board.repl.inject({
continuousServo
})
现在我们可以加载它并尝试一下!
在 REPL 中玩连续伺服电机
将你的project
文件夹加载到 Pi 上,在 Pi 的 SSH 会话中导航到该文件夹,并运行以下命令:
sudo node continuous-servo-repl.js
一旦你看到Board Initialized
,你就可以通过在 Johnny-Five REPL 中输入命令来尝试控制连续伺服电机:
continuousServo.cw(1) // see how fast it can go!
continuousServo.ccw(.5) // it changes directions near-instantaneously!
continuousServo.stop() // and stops very quickly, too!
正是因为能够快速改变方向和停止的能力,有些人更倾向于使用连续伺服电机而不是电机来为轮式机器人提供动力——总是有选择总是好的。
摘要
在本章中,我们学习了伺服电机与电机的区别,以及定期伺服电机和连续伺服电机的区别。我们还学习了 Johnny-Five 伺服对象的构造函数及其功能。接下来,我们构建了一个项目,教我们如何使用伺服对象分组伺服电机,并通过 REPL 从 Pi 的命令行控制它们。然后,我们构建了一个项目,展示了伺服电机通过构建光电池传递信息以及通过创建运动的能力。最后,我们更多地了解了连续伺服电机,并与之互动。
问题
-
伺服电机和电机的区别是什么?
-
定期伺服电机和连续伺服电机的区别是什么?
-
为什么伺服电机需要外部电源?
-
你会在什么情况下使用伺服电机而不是电机?
-
伺服对象有哪些好处?
第八章:动画库
伺服机构是我们机器人项目创建运动的优秀工具,但为了创建真正移动的行走机器人,我们需要更多的控制。每个伺服机构都不同;例如,每个伺服机构以略不同的最大速度移动。如果你想让机器人行走,你需要时间控制,以及知道伺服机构何时完成其运动的能力。这时就出现了动画库;这个 Johnny-Five 内部强大的工具允许你微调伺服机构运动,以获得更深入的控制。
本章将涵盖以下主题:
-
动画运动
-
动画库的术语
-
动画对象的构建
-
逐步进行伺服机构动画
-
学习更多关于排队和播放动画片段
-
动画对象事件
技术要求
你需要从上一章中使用的两个伺服机构设置,这就是本章的硬件要求。
本章的代码可以在github.com/PacktPublishing/Hands-On-Robotics-with-JavaScript/tree/master/Chapter08
找到。
动画运动
动画库使得使用伺服机构变得可能,否则这些事情可能从困难到完全不可能。然而,在我们探索动画库的“如何”之前,我们应该更彻底地解释“为什么”。
为什么我们需要动画库
当你迈步时,想想你的腿部运动。你通常不需要这样做,但当你这样做时,你的关节中会发生很多事情!你的臀部伸展你的腿,你的膝盖伸展你的腿而不通常锁定它。而且你的后腿也在做事情;你的臀部允许腿向后移动,你的脚踝在弯曲。这是一个巨大的简化,但它仍然非常复杂!
现在想象你的每个关节都是一个伺服机构,你需要编程来迈一步。你无法控制每个动作的时间,因为每个伺服机构会以你能达到的最快速度到达你告诉它的位置。你也不能知道一个关节何时完成运动,所以你必须硬编码时间并希望它能保持。
这种确切的问题正是动画库被制作出来要解决的。通过给你更多的控制,你就有更多的精确度。但我们的精确度指的是什么?
精确控制伺服机构运动
在移动伺服机构的背景下,真正的精确意味着能够控制所使用的伺服机构的时间、位置和速度。这种精确度在构建需要多个动作同步发生以避免物理碰撞的细致运动时至关重要。一个很好的例子是六足机器人:每个关节需要与其他腿部的关节同步移动,每条腿在迈步时需要精确地移动,以避免相互碰撞或使六足机器人失去平衡。
如果你使用硬编码来精确移动伺服电机,这将是一项艰巨的任务;想象一下,为了创建一个你希望持续一秒钟的动画,你需要设置 60 次servo.to()
调用。或者,使用servo.to()
硬编码每个动作,使用你放置在机器人腿部确切伺服电机的时间来计时,并且一切正常...直到伺服电机损坏(这是不可避免的),你必须更换它并重复整个校准过程。
Johnny-Five 中的动画库通过允许你将你的动作定义为更大设计的一部分,即动画本身,从而简化了此过程。它完成所有的数学运算并计算出所有的时间,以确保伺服电机在需要时处于正确的位置。
隐式使用动画库
有时候,你甚至不需要创建一个动画对象,就可以为你的伺服电机创建动画。在我们真正深入动画对象之前,让我们编写一些使用动画库隐式调用的代码。
使用servo.to()
隐式创建动画
在本章的project
文件夹中,创建一个名为implicit-animations.js
的文件。我们将设置此文件以使用 REPL 来演示不显式创建动画对象创建的动画。从正常的样板开始:引入johnny-five
和raspi-io
,像往常一样设置你的板和board.ready()
处理程序:
const Raspi = require('raspi-io')
const five = require('johnny-five')
const board = new five.Board({
io: new Raspi()
})
board.on('ready', () => {
})
然后,在board.on('ready')
处理程序内部,构建一个Servo
对象:
let servo = new five.Servo({
controller: "PCA9685",
pin: 0
})
接下来,仍然在board.on('ready')
处理程序内部,我们将创建相同的运动三次——每次使用不同的规格。其中两个将创建后台动画,一个则是默认运动,这样你可以看到差异。
如果你正在查看微型伺服电机叶片,可能会很难注意到伺服电机运动中的差异。为了使差异更容易看到,我在本章的伺服电机叶片上贴了一根冰棍棒。我还把它贴在桌子上,这样它就不会倒下,如下面的图所示:
第一个运动函数,仍然在board.on('ready')
处理程序内部,将被命名为normalFullSwing()
:
function normalFullSwing() {
servo.to(0)
servo.to(180)
}
此函数将尽可能快地将伺服电机移动到0
度,然后尽可能快地将伺服电机移动到180
度。
让我们在下一个函数中为servo.to()
添加一个参数,这将改变伺服电机到达所需位置所需的时间。我们将将其设置为接受一个时间参数,我们将在 REPL 中进行实验时传递它:
function timedFullSwing(time) {
servo.to(0)
servo.to(180, time)
}
此函数将接受传递给它的毫秒数,从0
度到180
度。它仍然会尽可能快地开始移动到0
度。
最后,我们将编写一个函数,该函数接受时间和步数参数,将伺服电机在给定的时间内从0
度移动到180
度,使用给定的步数。我们将此函数称为timedFullSwingWithSteps()
:
function timedFullSwingWithSteps(time, steps) {
servo.to(0)
servo.to(180, time, steps)
}
这个函数,和其他函数一样,仍然会尽可能快地先到达 0
。
最后,我们将使用 board.repl.inject()
给自己提供访问这些函数的权限:
board.repl.inject({
normalFullSwing,
timedFullSwing,
timedFullSwingWithSteps
})
然后我们就准备开始了(或者说,就像它那样摇摆)!
玩转隐式动画
将文件夹加载到你的 Pi 上,使用你的 Pi SSH 会话进入该文件夹,并运行以下命令:
npm init -y
npm i --save johnny-five raspi-io
现在我们已经设置好了,我们使用以下命令运行代码:
sudo node implicit-animations.js
一旦我们看到 Board Initialized
,我们就可以运行我们的函数并看到差异。这里有一些可以尝试的:
normalFullSwing() // hm...
timedFullSwing(1000) // wait a tick...
timedFullSwingWithSteps(1000, 10) // why didn't it do it?
到现在为止,你可能已经注意到有些不对劲。最多,你可能看到一两个抽搐,但这显然不是按预期工作的!
这是因为一个非常重要的时间问题,那就是如果你不等待伺服器运动完成,它们将相互覆盖,导致不稳定的结果。
这也是动画库之所以重要的部分原因!它具有让你排队动画的能力,这意味着伺服器会在进行下一个动作之前让每个动作完成,从而避免你需要自己编写等待代码(特别是考虑到 JavaScript 的异步性质,这尤其令人讨厌)。
现在,我们将使用更多的隐式动画和一些 setTimeout()
调用来使这些函数正常工作。
玩转隐式动画,再来一次
为了修复你的 normalFullSwing()
函数,我们将设置 servo.to(0)
调用为 250 毫秒,然后在 255 毫秒后调用 servo.to(180)
(只是为了确保它首先完全到达 0
):
function normalFullSwing() {
servo.to(0, 1000)
setTimeout(() => { servo.to(180) }, 1010)
}
我们将对 timedFullSwing()
和 timedFullSwingWithSteps()
函数做同样的处理:
function timedFullSwing(time) {
servo.to(0, 1000)
setTimeout(() => { servo.to(180, time) }, 1010)
}
function timedFullSwingWithSteps(time, steps) {
servo.to(0, 1000)
setTimeout(() => { servo.to(180, time, steps) }, 1010)
}
一旦你做了这些更改,重新加载你的 project
文件夹并运行它:
sudo node implicit-animations.js
如果你直接使用书中的代码而不是跟随操作,implicit-animations-fixed.js
包含了超时设置,所以你应该运行那个文件而不是再次运行 implicit-animations.js
。
现在代码按预期运行,让我们再对这些隐式动画玩玩:
// Just a normal swing
normalFullSwing()
// Playing with the time parameter
timedFullSwing(1000)
timedFullSwing(750)
timedFullSwing(5000)
// Playing with the steps parameter
timedFullSwingWithSteps(3000, 2)
timedFullSwingWithSteps(1000, 10)
timedFullSwingWithSteps(1000, 100)
在心中记下改变时机和步骤对伺服器运动的影响,这对于本章的其余部分会很有帮助。
现在我们已经理解了一些动画库的底层效果,以及为什么在处理复杂的伺服器运动时它如此关键,让我们详细地研究一下动画库。我们将从术语开始,除非你拥有动画(如动画图像)的强大背景,你还需要学习一些词汇!
动画库的术语
动画库被命名为这种方式是非常有意的;动画对象的词汇非常接近动画图像的词汇。让我们看看我们将在这章中大量使用的几个术语。
-
帧:在这个上下文中,动画的帧是伺服系统在特定时间点的状态。正如你可以想象的那样,为复杂的伺服系统组(如肢体)编程每一帧伺服运动将是一场噩梦。幸运的是,技术站在我们这边,我们不需要编写每一帧。
-
关键帧:关键帧是动画中的一个点,它作为锚点,除非你手动绘制(或编程)动画的每一帧;你建立一组关键帧,以确定动画的主要运动点。例如,在我们之前所做的完整扫描中,一组好的关键帧可能如下所示:
-
从任何角度开始
-
处于
0
度 -
在
180
度时结束
-
简单的动画拥有较少的关键帧,但你始终需要至少两个来创建一个动画。请注意,关键帧本身并没有任何时间概念;它们必须与提示点结合来创建动画。
-
提示点:提示点是在序列上下文中介于 0 和 1 之间的一个点,一组与等大小的关键帧集和整体持续时间配对的提示点创建了一个完整的动画。例如,当我们使用包含 0 秒、1 秒和 2 秒的提示点集与上述关键帧集配对时,你得到一个听起来像动画的东西:
-
在 0%时从任何角度开始
-
在 50%时处于
0
度 -
在
180
度时结束于 100%
-
-
持续时间:持续时间是动画序列将运行的时间长度,当与关键帧和提示点配对时,完成动画。以上述 2000 毫秒的持续时间为例,你得到:
-
在 0 毫秒时从任何角度开始
-
在 1000 毫秒时处于
0
度 -
在 2000 毫秒时结束于
180
度
-
-
补间:补间是软件在关键帧之间创建必要帧的想法。你建立关键帧,补间则确定这些帧之间的内容。每帧之间的时间(由我们的
timedFullSwing()
函数展示)和关键帧之间的步数(帧数)(由我们的timedFullSwingWithSteps()
函数展示)使我们能够微调补间过程。 -
缓动:缓动是补间过程的一部分。没有缓动,所有的补间都是线性完成的,每个补间帧的运动量相同。如果你在构建试图行走的东西,这看起来一点也不平滑。存在几种补间形式;最常见的一种是缓入或缓出缓动,分别以缓慢开始并逐渐加速到快速结束,或者以快速开始并逐渐减速到缓慢结束。
现在我们已经讨论了动画的术语,我们可以开始用 Johnny-Five 编写我们的第一个(显式)动画对象了!
动画对象的构建
要构建一个动画对象,我们需要创建对象本身,创建一组关键帧和一组提示点,然后将这些关键帧和提示点作为动画排队在我们的伺服器上运行。
创建动画对象
在你的project
文件夹中创建一个名为my-first-animation.js
的新文件,并创建正常的样板:在 Johnny-Five 和 Raspi-IO 中require
,创建你的Board
对象,并创建board.on('ready')
函数:
const Raspi = require('raspi-io')
const five = require('johnny-five')
const board = new five.Board({
io: new Raspi()
})
board.on('ready', () => {
})
然后,在board.on('ready')
处理程序内部,在我们的 PWM 帽的0
引脚和1
引脚上构建我们的两个Servo
对象:
let servoOne = new five.Servo({
controller: "PCA9685",
pin: 0
})
let servoTwo = new five.Servo({
controller: "PCA9685",
pin: 1
})
并创建一个包含我们的伺服器的Servos
对象:
let servos = new five.Servos([servoOne, servoTwo])
现在我们有一组伺服器,我们可以创建一个动画对象:
let myFirstAnimation = new five.Animation(servos)
现在我们有了动画对象,是时候规划我们的动画序列,设置关键帧和提示点,并将它们排队进行动画。
规划动画序列
让我们规划一个足够简单的动画来进行第一次尝试:让我们允许伺服器从任何地方开始,然后servoOne
将移动到0
度,而servoTwo
将移动到180
。然后,servoOne
将扫过到180
度,而servoTwo
开始向90
度移动,然后两个伺服器都将结束在90
度。让我们让这些位置每两秒发生一次。因此,我们的关键帧看起来可能像这样:
-
以任何度数开始
servoOne
,以任何度数开始servoTwo
-
将
servoOne
移动到0
度,将servoTwo
移动到180
度 -
servoOne
保持在它最后已知的位置,servoTwo
正在向90
度移动 -
将
servoOne
移动到180
度,servoTwo
正在向90
度移动 -
将
servoOne
移动到90
度,将servoTwo
移动到90
度完成
我们的提示点(以0
-1
表示):0
,.25
,.5
,.75
,1
。
现在我们已经规划好了我们的序列,我们可以开始编程我们的关键帧。
创建关键帧
我们需要为我们的伺服器组中的每个伺服器创建一个关键帧数组,对于每个提示点:两个包含五个关键帧的数组。
这听起来很简单,但我们如何让动画让伺服器从它们所处的任何位置开始?以及我们如何让servoTwo
在移动到90
度的过程中跨越两个提示点进行缓动?答案在于 Johnny-Five 如何解析 null 和 false 作为关键帧中的伺服器位置。
在关键帧中使用 null 和 false 作为位置
Johnny-Five 使用 null 和 false 允许我们创建复杂的段,在这些段中,我们可以在多个提示点之间进行缓动,或者使用伺服器的最后已知位置作为关键帧位置。
空对象的效果取决于它被使用的位置,如果它在第一个关键帧中使用,它将使用伺服器的位置作为动画开始时的关键帧位置。这正是我们开始动画序列所需要的,因为我们希望两个伺服器都从它们恰好所在的位置开始。如果空对象在不是第一个的关键帧中使用,那么在那个提示点处,关键帧将被基本忽略;如果你在一个关键帧中有 30 度,下一个关键帧中是空对象,第三个关键帧中是 120 度,伺服器将在两个提示点之间移动90
度。我们将使用这一点来允许servoTwo
在两个提示点之间从180
度移动到90
度。
当你使用false
作为关键帧位置时,它将使用在最后一个关键帧中设置的位置。我们将使用这一点在servoOne
的关键帧调用要求伺服器保持在最后已知位置时,而不是硬编码第二个 180 度的位置。
现在我们知道了空和false
如何影响我们的关键帧定位,让我们为我们的计划动画序列编程我们的关键帧。
编程我们的关键帧
根据我们提供的信息,每个关键帧所需的值如下:
-
servoOne null
,servoTwo null
(从伺服器恰好所在的位置开始) -
servoOne 0
,servoTwo 180
-
servoOne 180
,servoTwo null
(servoTwo
开始向90
度移动) -
servoOne false
,servoTwo null
(servoOne
保持不动,servoTwo
仍在向90
度移动) -
servoOne 90
,servoTwo 90
每个位置都需要是一个对象,具有每个关键帧的degrees
属性。让我们将其转换为 JavaScript,就在我们的动画对象构建下方:
let keyframes = [
[null, {degrees: 0}, {degrees: 180}, false, {degrees:90}], // servoOne
[null, {degrees: 180}, null, null, {degrees: 90}] // servoTwo
]
现在我们已经编程了关键帧,让我们开始设置提示点。
设置提示点和持续时间
提示点,无论你有多少伺服器,都将始终是一个一维数组的时间,以匹配你传递到关键帧数组中的每个关键帧。
注意,尽管这个动画中的提示点均匀分布,但这完全是可选的,你的提示点可以彼此之间距离很远,而不会破坏任何东西。
在我们的关键帧对象下方,让我们设置我们的提示点数组:
let cuePoints = [0, .25, .5, .75, 1]
我们希望整个动画持续 8 秒,所以添加:
let duration = 8000
我们已经有了所有需要的数据,让我们制作一个动画!
将所有内容组合起来制作动画
为了运行我们的动画序列,我们必须使用Animation.enqueue()
函数将其放入队列中。我们需要一起传递持续时间、关键帧和提示点。在你的my-first-animation.js
中,在持续时间之后添加:
myFirstAnimation.enqueue({
duration: duration,
keyFrames: keyframes,
cuePoints: cuePoints
})
包含持续时间、keyFrames
和cuePoints
属性的对象在动画库中被称为Segment
对象。
动画段将在排队后立即开始播放,所以我们准备好加载我们的项目并看到一些动画伺服器!
观看你的动画运行
将你的project
文件夹加载到 Pi 上,在 Pi SSH 会话中导航到该文件夹,并运行:
sudo node my-first-animation.js
你应该会看到动画以我们描述的方式播放,两个伺服电机也是如此。
这真的很强大,但当你想到一个六足步行机器人时,这些线性运动不会使步态看起来真实或美观。让我们在我们的动画序列中添加一些缓动,以创建一些更有机的外观运动。
缓动到伺服电机动画
除非你希望你的未来行走机器人非常坚定地处于“不祥谷”,否则你需要使用缓动来创建更流畅、更自然的动画片段运动。
缓动如何适应动画片段
伺服电机的keyframes
中添加了缓动函数;因此,我们不仅说明了希望伺服电机到达的位置,还说明了它是如何到达那里的。例如,这些keyframes
:
let keyframes = [
null,
{degrees: 180, easing: 'inoutcirc'}
]
将从任何位置开始的伺服电机移动到180
,开始时速度较慢,中间加速,然后再次减速。
缓动有许多不同的选项,它们在作为 Johnny-Five 依赖项包含的ease-component
(www.npmjs.com/package/ease-component
) npm
模块中有文档说明。我们将从incirc
、outcirc
和inoutcirc
开始使用。
为我们的第一个动画添加缓动
将my-first-animation.js
的内容复制到一个名为easing-animations.js
的新文件中。接下来,我们将修改keyframes
数组以包含一些缓动:
let keyframes = [
[null, {degrees: 0}, {degrees: 180, easing: 'inOutCirc'}, false, {degrees:90, easing:'outCirc'}], // servoOne
[null, {degrees: 180}, null, null, {degrees: 90, easing:'inCirc'}] // servoTwo
]
让我们也将动画片段的持续时间增加,以便我们真正看到缓动带来的差异:
let duration = 16000
然后,将其加载到树莓派上,在您的树莓派 SSH 会话中导航到文件夹,并运行以下命令:
sudo node easing-animations.js
真实地观察inCirc
、outCirc
和inOutCirc
如何影响你的动画。
缓动整个动画片段
为了轻松地将动画片段中的所有关键帧设置为具有相同的缓动,你可以在排队你的片段时传递一个easing
属性。例如:
myFirstAnimation.enqueue({
keyFrames: keyframes,
duration: duration,
cuePoints: cuePoints,
easing: 'inOutCirc'
})
上述代码将覆盖关键帧,并且所有这些都将使用inOutCirc
缓动。现在我们已经完全探索了缓动动画片段,让我们看看动画队列以及我们如何在排队和播放时影响我们的片段。
学习更多关于排队和播放动画片段
当我们排队一个动画片段时,我们传递给它持续时间、提示点和关键帧。但我们也可以传递其他影响动画片段播放的选项。我们还可以调用动画对象上的方法,这些方法会影响当前正在播放和排队中的动画片段。
在我们开始修改这些之前,将easing-animations.js
的内容复制到一个名为playing-with-the-queue.js
的新文件中。删除末尾对myFirstAnimation.enqueue()
的调用;这次我们想要一点控制权。
循环动画片段
首先,让我们添加一个函数,以便我们正常排队动画:
function playMyAnimation() {
myFirstAnimation.enqueue({
keyFrames: keyframes,
duration: duration,
cuePoints: cuePoints
})
}
有时候你希望你正在排队的动画段在循环中运行。让我们在board.on('ready')
处理程序中创建一个函数,该函数将循环我们的动画段:
function loopMyAnimation() {
myFirstAnimation.enqueue({
keyFrames: keyframes,
duration: duration,
cuePoints: cuePoints,
loop: true
})
}
你还可以添加loopBackTo
属性,并将其设置为提示点的索引;动画将从指定的提示点开始循环。
如果我们想让动画向前播放,然后回到起点并重复,我们应该如何编写一个函数来设置metronomic
属性以实现这一点?
function metronomeMyAnimation() {
myFirstAnimation.enqueue({
keyFrames: keyframes,
duration: duration,
cuePoints: cuePoints,
metronomic: true
})
}
现在我们知道了如何循环和节拍动画段,让我们来探索使用动画对象更改动画段速度的方法。
改变动画段的速度
你可以用一个数值乘数调用Animation.speed()
来改变当前动画段的速度。例如,调用Animation.speed(.5)
将减半速度,而Animation.speed(2)
将加倍它。
让我们编写一些函数来将动画段的速度减半、加倍和归一化:
function halfSpeed() {
myFirstAnimation.speed(.5)
}
function doubleSpeed() {
myFirstAnimation.speed(2)
}
function regularSpeed() {
myFirstAnimation.speed(1)
}
将这些添加到循环和节拍器功能中。
现在我们知道了如何调整动画函数的速度以及如何循环它们,让我们来谈谈暂停、播放和停止动画。
播放、暂停和停止动画段
如果不进行干预,动画段将一直播放,直到队列中没有剩余的内容可以播放(这意味着如果有一个循环或节拍器段,它将停留在该段上)。
但你可以移动到下一个动画:
Animation.next()
或者你可以暂停当前段:
Animation.pause()
再次启动它:
Animation.play()
或者停止当前段并清除整个队列:
Animation.stop()
让我们使用这些,以及 REPL,来玩转我们的动画和我们新发现的能力来操纵它。
在 REPL 中将所有这些整合在一起
将以下内容添加到playing-with-the-queue.js
中board.on('ready')
处理程序的末尾:
board.repl.inject({
myFirstAnimation,
playMyAnimation,
loopMyAnimation,
metronomeMyAnimation,
halfSpeed,
doubleSpeed,
normalSpeed
})
然后,将你的project
文件夹加载到你的 Pi 上,在 Pi SSH 会话中导航到project
文件夹,并运行以下命令:
sudo node playing-with-the-queue.js
一旦看到Board Initialized
,尝试一些命令来实验动画的播放方式:
loopMyAnimation()
myFirstAnimation.pause()
myFirstAnimation.play()
halfSpeed()
myFirstAnimation.stop()
metronomeMyAnimation()
doubleSpeed()
playMyAnimation()
myFirstAnimation.next()Summary
概述
在本章中,我们使用伺服电机深入研究了动画库。我们学习了动画库的关键术语,如何构建动画段,如何排队,以及如何在排队段或通过调用动画对象的方法来操作播放。
问题
-
为什么对于多个伺服电机的复杂运动,动画是必要的?
-
关键帧是什么?
-
提示点是什么?
-
动画段由哪三部分组成?
-
缓动对动画关键帧和段有什么影响?
-
动画对象中的哪个方法可以停止当前段并清除动画队列?
-
调用
Animation.speed(.25)
会对当前动画做什么?
第九章:获取你需要的信息
我们让我们的 Pi 发现其周围环境,并通过各种方式显示数据。我们甚至赋予了它们移动的能力!但是,还有大量的数据需要收集,有时你想要的数据无法在本地收集。这就是互联网和使更多数据免费可用的倡议发挥作用的地方。在本章中,我们将探讨如何将你的 Pi 连接到互联网并获取天气信息,以便创建天气仪表板。
本章将涵盖以下主题:
-
为什么要把你的 NodeBots 连接到互联网?
-
使用 OpenWeatherMap 在我们的 Pi 上获取天气数据
-
使用 LCD 构建天气仪表板
-
使用 Pi 爬取网站上的数据
技术要求
对于这个项目,你将需要你的 Pi 和一个带有 I²C 接口的 LCD 字符显示。你可以从 Adafruit 购买并焊接一个 LCD (www.adafruit.com/product/198
) 和背包 (www.adafruit.com/product/292
),或者通过 SainSmart (www.amazon.in/SainSmart-Serial-Module-Shield-Arduino/dp/B00AE0FRDQ/
) 购买预构建模块。
你还想要确保你的 Pi 可以通过互联网访问外部世界,正如我们在第一章,设置你的开发环境中设置的那样。
本章的代码可在以下位置找到:github.com/PacktPublishing/Hands-On-Robotics-with-JavaScript/tree/master/Chapter09
。
为什么要把你的 NodeBots 连接到互联网?
虽然传感器可以提供本地数据,但有时你希望显示来自远方或连接到其他设备上的传感器的数据。这正是我们可以真正利用 Node.js 和 npm 包来为我们的 Raspberry Pi 项目带来便利的地方。
使用 npm 模块的力量
在第二章,创建你的第一个 Johnny-Five 项目中,我们使用了 color npm
模块来管理颜色。我们使用了 barcli
模块将传感器数据放入条形图中。现在是时候使用请求 npm
模块为我们从网站上检索数据了!这允许我们简化使用 C 编写的微控制器开发,无需每次都手动创建 HTTP 请求,并且能够使用异步调用。
对于那些不熟悉请求模块的人来说,我们将使用它来执行类似于这样的 HTTP GET 请求:
const request = require('request')
request.get(url, (err, response, body) => {
console.log(body)
})
我们给 request.get()
调用一个 URL 和一个回调,该回调接收一个错误(希望是 null),一个响应对象,以及一个体,它方便地从完整的请求对象(可能非常大且复杂)中提取出来。
使用你收集的数据
你可以使用从互联网收集的数据进行许多不同的项目:
-
我在实验室里有一串可以由 Twitch 实时聊天控制的灯光
-
你可以在本地项目中比较来自远方的信息
-
你可以使用随机数据!马尔可夫链和其他半随机数据可以用于有趣的项目
以下是一些你将想要了解的,将进入你的数据收集项目:
-
这是一个 REST API 吗?我会得到 JSON 数据还是需要解析?
-
这是在抓取 HTTP 网站吗?我将如何解析出我正在寻找的 HTML 数据?(注意:如果你要抓取的网站经常更改,这可能会变得很棘手,也可能很脆弱。)
-
我需要 API 密钥或 JSON Web 令牌(JWT)来进行身份验证吗?
一些需要注意的事项
在你的 Pi 项目上进行互联网数据收集时,以下是一些需要注意的事项:
-
Wi-Fi 消耗的电量很多,所以如果你的项目是使用电池运行的,你需要注意功耗。
-
使用你的机器人力量做好事,不要构建有害的项目,收集他们不应该收集的信息,或者有其他可疑的目的!
-
在 Pi 上解析大量的 JSON 或 HTML 响应可能需要一段时间,所以如果你的项目运行得有点慢,请查看你得到的内容。
使用 OpenWeatherMap 在我们的 Pi 上获取天气数据
我们将为此构建一个天气机器人,虽然我们可以使用温度传感器,但这只会告诉我们室内的情况,而我们通常在出门前想看看外面的天气。因此,我们将使用 OpenWeatherMap API 来获取数据并在字符 LCD 上显示;但让我们先从获取 API 到 Pi 的数据开始,然后再开始跑。
获取 OpenWeatherMap API 密钥
首先,你需要在openweathermap.org/
注册一个账户,并生成一个 API 密钥。然后,点击页面左上角的用户名,并从顶部出现的标签中选择 API Keys:
在此页面上生成一个 API 密钥并保持标签页打开;我们将在下一节中使用它来获取所需的信息。
接下来,在另一个标签页中打开 API 链接;你将在这个页面上看到主要的 API 功能调用。我们要找的就在顶部 – 当前天气数据。
点击 API 文档按钮,我们将确定需要向哪个 URL 发起请求。在撰写本文时,URL 如下:
http://api.openweathermap.org/data/2.5/weather?q=[city]&appid=[your API key]
如果你用你的城市和 API 密钥填写浏览器中的该 URL,你应该会看到如下内容:
{"coord":{"lon":-97.74,"lat":30.27},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"base":"stations","main":{"temp":305.59,"pressure":1016,"humidity":46,"temp_min":304.15,"temp_max":307.15},"visibility":16093,"wind":{"speed":3.6,"deg":170,"gust":8.2},"clouds":{"all":1},"dt":1534470960,"sys":{"type":1,"id":2558,"message":0.0037,"country":"US","sunrise":1534507156,"sunset":1534554612},"id":4671654,"name":"Austin","cod":200}
如果这些温度看起来有点高(即使是对于德克萨斯州的奥斯汀来说),那是因为它们是以开尔文为单位的。在我们的项目中,我们将通过设置 URL 中的单位参数为metric
来获取摄氏度,或者为imperial
获取华氏度。
现在我们已经准备好在npm
请求模块中编写代码,并将数据传输到我们的 Pi 上。
正在发送请求
让我们编写一个基本的程序,不使用 Johnny-Five,在添加 LCD 之前在我们的 Pi 上收集数据。在你的 Pi 上的一个文件中,或者准备好移动到 Pi 上的文件,命名为weather-test.js
:
const request = require('request')
setInterval(() => {
request({
url: 'http://api.openweathermap.org/data/2.5/weather',
qs: {
q: [your city],
appid: [your API key],
units: ['metric' or 'imperial']
},
json: true // returns the parsed json body for us
}, (err, resp, body) => {
console.log(body)
})
}, 60000)
解析响应
控制台打印出的 JSON 对象看起来大致如此(格式化以便于阅读):
{
"coord":{"lon":-97.74,"lat":30.27},
"weather":[
{"id":800,"main":"Clear","description":"clear sky",
"icon":"01n"}
],
"base":"stations",
"main":{
"temp":305.59,
"pressure":1016,
"humidity":46,
"temp_min":304.15,
"temp_max":307.15
},
"visibility":16093,
"wind":{
"speed":3.6,
"deg":170,
"gust":8.2
},
"clouds":{"all":1},
"dt":1534470960,
"sys":{
"type":1,
"id":2558,
"message":0.0037,
"country":"US",
"sunrise":1534507156,
"sunset":1534554612
},
"id":4671654,
"name":"Austin",
"cod":200
}
那是大量的天气数据!幸运的是,因为npm
模块请求在选项中传递了json
: true 属性,它假设返回的任何内容都是 JSON,并为你解析它,所以你可以立即访问数据属性:
let longitude = body.coords.lon // -97.74
let conditions = weather.condition // 'clear sky'
let currentTemp = main.temp // 305.59 degrees Kelvin
使用 LCD 构建天气仪表盘
现在我们有了天气数据,是时候将字符 LCD 连接到我们的 Pi 上,并使用它来显示天气数据了。我们将探索 Johnny-Five LCD 对象,将其连接到 Pi,并使用 Johnny-Five 和npm
请求模块将它们全部编码在一起。
将 LCD 添加到 Pi 上
参考以下图解进行连接:
请记住,带有 I²C 接口的背板位于 LCD 的背面;我在图中将其向前移动,以便帮助你看到与 Pi 的连接。
LCD 对象
让我们看看 Johnny-Five 文档中的 LCD 对象,以便了解如何在我们的天气仪表盘代码中构建和使用我们的 LCD。
构建我们的 LCD
通常,没有 I2C 的 LCD 最多可以占用八个引脚!这很多,我喜欢在我的机器人项目中使用尽可能少的线(以后更容易调试)。有了我们的背板,我们只需要两个电源引脚和两个 I2C 引脚。但这也意味着我们需要找到我们的控制器——如果你使用的是 Adafruit 背板,那么我们的控制器就是PCF8574
;如果你购买了另一个背板,请确保它使用 PCF8574x 芯片之一!
我们还需要 LCD 的行和列字符大小——大多数是 2 行 16 列,但你可能使用了 4 行 20 列的字符模型。在任一情况下,使用适用于你连接到背板的 LCD 的任何大小。
考虑到所有这些,我们的构造函数应该看起来像这样:
let LCD = new five.LCD({
controller: "PCF8574",
rows: 2,
cols: 14
});
现在我们已经构建了 LCD,让我们看看我们需要设置什么,以及如何在屏幕上显示字符!
设置 LCD
我们需要做的第一件事是打开背光:
LCD.on()
然后,我们想要让闪烁的光标消失:
LCD.noBlink()
现在,我们准备好学习如何移动光标、打印语句和清空 LCD。
打印到 LCD 并清空 LCD
在打印之前,我们想要确保 LCD 被清空:
LCD.clear()
并且光标位于起始位置(行 0,列 0):
LCD.home()
接下来,我们可以打印到 LCD:
LCD.print("Hello, World!")
注意,你还可以将 LCD 函数链接在一起,因为 Johnny-Five 从每个对象函数返回 LCD 对象:
LCD.clear(),home(),print('Hello World!')
现在,我们已经拥有了开始制作我们的仪表盘所需的一切!
将所有这些编码在一起
我们需要将本章学到的知识整合起来。首先,在你的project
文件夹中创建一个名为weather-dashboard.js
的文件,并设置你的 Johnny-Five、Raspi-IO 和 request 库,构建你的Board
对象,并创建board.on('ready')
处理程序:
const Raspi = require('raspi-io')
const five = require('johnny-five')
const request = require('request')
const board = new five.Board({
io: new Raspi()
})
board.on('ready', () => {
})
然后,在board.on('ready')
处理程序内部,构建和设置我们的 LCD:
let LCD = new Five.LCD({
controller: 'PCF8574'
})
LCD.noBlink()
LCD.on()
然后,我们将创建一个获取天气数据的函数,并将其设置为每分钟一次的间隔:
function getWeather() {
request({
url: 'http://api.openweathermap.org/data/2.5/weather',
qs: {
q: [your city],
appid: [your API key],
units: ['metric' or 'imperial'],
json: true
}
}, (err, resp, body) => {
})
}
setInterval(getWeather, 60000)
在请求回调中,我们将清除并写入 LCD:
LCD.clear()
LCD.home().print('Temp: ' + body.main.temp + ' Deg [F or C]')
LCD.setCursor(0, 1).print(body.weather.description)
最后,在开始时调用getWeather()
函数,以防止项目在显示任何内容之前需要整整一分钟:
getWeather()
一旦你将全部代码整合完毕,将project
文件夹加载到我们的 Pi 上,在 Pi 的 SSH 会话中导航到该文件夹,并运行以下命令:
npm i
sudo node weather-dashboard.js
你应该能看到你输入的城市温度和条件出现在 LCD 上,并且它们应该每分钟刷新一次。
现在我们已经看到了一个项目,其中 Pi 从整洁的 JSON REST API 中获取数据,让我们尝试从更复杂的数据源获取数据:HTML 抓取。
项目 – 使用 Pi 从网站抓取数据
HTML 抓取是指向网页发起请求以获取 HTML 本身的过程,以便从中解析出数据。我们将构建一个机器人,通过抓取downforeveryoneorjustme.com/
,一个告诉你网站是否宕机的网站,来显示johnny-five.io
是否正常运行。
对于这个项目,你不需要更改天气仪表板的布线设置,我们当前所需的硬件就足够了。
从 downforeveryoneorjustme.com 抓取 johnny-five.io
首先,访问downforeveryoneorjustme.com/
并在 URL 输入框中输入johnny-five.io
,然后按Enter。你应该会到达downforeveryoneorjustme.com/johnny-five.io
,希望你会看到一个相当简单的页面,如下所示:
现在为了准备我们的网络抓取代码,我们需要知道我们要查找的 HTML 元素以及 URL。右键点击“只是你”并选择“检查”(或你浏览器上的任何变体。在 Chrome 中,你会看到如下内容):
这就是网络抓取的一个风险:有时候没有太多东西可以用来找到你的元素。我们最接近的是 ID 为domain-main-content
的 div 的第一个段落(p
标签)。我们将想要查看它是否包含字符串It's just you.
,以确定johnny-five.io
是否正常运行。
现在我们有了 URL 和预期的元素以及解析标准,让我们通过将 HTML 引入 Johnny-Five 项目来开始编码。
发起 HTTP 请求
在你的project
文件夹中创建一个名为scraper-j5-alert.js
的新文件。从正常的库开始,构建Board
,并设置board.on('ready')
处理程序。别忘了包含npm
模块的请求:
const Raspi = require('raspi-io')
const five = require('johnny-five')
const request = require('request')
const board = new five.Board({
io: new Raspi()
})
board.on('ready', () => {
})
然后,在你的board.on('ready')
处理程序内部,构建并设置你的 LCD 对象:
let LCD = new Five.LCD({
controller: 'PCF8574'
})
LCD.noBlink()
LCD.on()
然后,我们将创建一个函数来获取来自downforeveryoneorjustme.com/johnny-five.io
的 HTML,并将其放置在五分钟间隔上。最后,我们调用它,这样我们就不必等待五分钟才能得到第一个结果:
function isJohnnyFiveDown() {
request('https://downforeveryoneorjustme.com/johnny-five.io',
(err, resp, body) {
console.log(body)
})
}
setInterval(isJohnnyFiveDown, 300000)
isJohnnyFiveDown()
将project
文件夹加载到你的树莓派上,在树莓派的 SSH 会话中导航到该文件夹,并运行以下命令:
sudo node scraper-j5-alert.js
你可能几秒钟后会在控制台看到类似的内容(我只是截图了一小部分):
但我们如何从那个巨大的字符串中获取信息?正则表达式?请,不要,不是那些。幸运的是,正如 Stilwell 定律(见第二章,创建你的第一个 Johnny-Five 项目)所述,如果你能想到功能,那么在npm
上就有一个相应的包。在这种情况下,我们有 cheerio 模块,它允许我们使用 JQuery 风格的 API 解析和查询 HTML 字符串。
使用 Cheerio 获取我们想要的元素
在你的project
文件夹中,运行以下命令:
npm i --save cheerio
Cheerio 的基本用法是通过调用来解析文本:
const cheerio = require('cheerio')
const $ = cheerio.load(htmlText)
然后,使用$
变量进行查询,就像使用 JQuery 一样(如果你从未使用过 JQuery,请参阅进一步阅读以获取有关选择元素的优秀入门指南):
let divWithIDHello = $('#hello')
let helloDivText = divWithIDHello.text()
解析 HTML 并显示结果
这就是我们需要的,用于抓取 HTML 并获取状态。在scraper-alert-j5.js
内部,我们将添加 cheerio 的require()
调用到文件的顶部,与其他调用一起:
const cheerio = require('cheerio')
然后,我们将修改在请求完成抓取 HTML 时触发的回调。我们将添加 cheerio 调用以加载文本,并查找 ID 为domain-main-content
的 div 的第一个p
子元素,并提取其文本。然后,我们将检查该文本是否包含It's just you.
,并将其写入 LCD:
function isJohnnyFiveDown() {
request('https://downforeveryoneorjustme.com/johnny-five.io',
(err, resp, body) => {
let $ = cheerio.load(body)
let statusText = $('#domain-main-content p')[0].text()
LCD.clear()
LCD.home()
LCD.print('johnny-five.io')
LCD.cursor(0, 1)
// make sure to use " to surround the string!
if(statusText.contains("It's just you.")){
LCD.print('is up!')
} else {
LCD.print('is down (possibly)!')
}
})
}
我们已经准备好加载并运行它!将你的项目加载到树莓派上,在树莓派的 SSH 会话中导航到该文件夹,并运行以下命令:
npm i
sudo node scraper-alert-j5.js
你应该在 LCD 上看到 Johnny-Five 是否启动!
你可能已经注意到我在下线条件中加入了可能
这个词。这是因为,正如我之前提到的,HTML 抓取非常脆弱。如果他们将It's just you.
改为It is just you.
,我们的代码就会崩溃!所以我喜欢提醒 LCD 观众,它可能不一定真的下线。这再次是为什么,如果你能找到,最好从 API 获取数据的原因。但有时没有真正的选择。
摘要
在本章中,我们使用 I²C LCD 屏幕、npm 模块知识和 REST API 构建了一个天气仪表板,并利用了 Node.js 和树莓派的力量。你可以利用这些技能继续构建许多新的项目;如果你可以从互联网上获取信息,你可以在 Johnny-Five 和树莓派项目中使用它。
问题
-
为什么树莓派非常适合需要远程数据的项目?
-
在从树莓派发送常规网络请求时,需要考虑哪些因素?
-
为什么我们可以链式调用 LCD 对象,例如
LCD.clear().home()
? -
为什么我们使用 I2C 背板与我们的 LCD 一起使用?
-
如果不使用背板,我们需要更多组件来使用 LCD 吗?
-
LCD.on()
是否打开了整个 LCD?如果不是,它做什么?
进一步阅读
-
request npm 模块页面:
www.npmjs.com/package/request
-
完整的 OpenWetherMap API:
openweathermap.org/api
-
Johnny-Five LCD 文档:
johnny-five.io/api/lcd/
-
cheerio npm 模块页面:
www.npmjs.com/package/cheerio
-
JQuery '选择元素'教程:
learn.jquery.com/using-jquery-core/selecting-elements/
第十章:使用 MQTT 与互联网上的事物通信
物联网设备可以通过多种方式通信,其中一些方式已经成为了标准。我们将探讨物联网设备通信的几种方式,然后深入探讨一个标准,MQTT。然后我们将构建一个小项目,使我们能够通过 AdafruitIO 查看和发送 MQTT 事件,AdafruitIO 是一个提供在线 MQTT 代理服务的平台。
本章将涵盖以下主题:
-
物联网设备通信
-
MQTT - 一个物联网发布/订阅协议
-
在树莓派上使用 AdafruitIO 设置 MQTT
-
项目 - 添加 LCD 和按钮以查看和显示 MQTT 事件
-
项目 - 使用 IFTTT 的社交媒体通知机器人
技术要求
对于这个项目,你需要将你的 LCD 连接到你的树莓派上(参见第九章,获取所需信息),一个按钮和一个 10K 欧姆电阻作为硬件。
对于其他工具,你需要在adafruit.io
创建一个免费账户。你还需要一个 IFTTT 账户(也是免费的)来完成最终项目。
本章的代码位于github.com/PacktPublishing/Hands-On-Robotics-with-JavaScript/tree/master/Chapter10
。
物联网设备通信
正如我们在第九章,获取所需信息中看到的,我们的树莓派可以使用 HTTP 请求从互联网上获取信息。但如果我们想实时将数据发送到树莓派呢?如果我们想一组设备相互聊天,根据需要来回发送数据呢?让我们看看使用 Web 技术可以如何实现这一点。
长轮询
长轮询涉及在特定间隔通过 HTTP 请求获取信息。如果这听起来很熟悉,那是因为这正是我们在第九章的天气仪表板项目中做的;我们每 60 秒轮询一次 OpenWeather API。当没有其他选择时,这种方法是最好的;一些 REST API 没有保持连接开启或建立双向通信的方法,在这些情况下,长轮询是最佳选择。
但现在有更新的方法来建立可以保持开启的双向连接,包括 WebSocket。
WebSocket
WebSocket 是一个强大的工具,它允许我们建立一个保持开启的双向数据连接,直到你关闭它(除非出现错误或连接丢失)。你可以实时地来回发送消息,而不需要每次都设置一个新的连接。它还允许服务器在与你的树莓派通信时无需询问,这对于实时数据来说是非常好的。
虽然 WebSockets 功能强大,但有一些方法可以针对物联网项目微调此连接。同时,保持一个开放的套接字并保持发送或接收的数据组织起来可能也很困难。考虑到这一点,我们将讨论 MQTT。
MQTT - 一个物联网发布/订阅协议
消息队列遥测传输(MQTT)协议(通常发音为mosquitto)是为低带宽、高延迟环境设计的协议,这使得它非常适合物联网项目,尤其是那些在有限硬件上运行的项目。它不仅用于机器之间的通信:一些项目使用它来发送用于存储的数据。
让我们看看 MQTT 是如何工作的,以及它是如何为我们的项目实现发布/订阅设置的。
MQTT 的基本知识
让我们先概述几个术语,然后将它们联系起来,以定义 MQTT 作为一个概念:
-
MQTT 消息: MQTT 消息由主题和消息组成。主题是客户端订阅的,他们通常读取消息以获取数据。
-
MQTT 客户端: MQTT 客户端连接到 MQTT 代理,一旦连接到代理,就可以订阅和发布主题。
-
MQTT 代理: MQTT 代理负责处理客户端连接,并在客户端在某个主题上发布消息时,将消息传递给所有订阅该主题的客户端。它还可以向主题发布消息,这些消息将发送给所有订阅的客户端。
在我们的项目中,我们将设置 Pi 作为 MQTT 客户端,将其连接到 AdafruitIO 的代理,并发布代理将发送给客户端(我们的 Pi)的消息,并接收它发布的消息。
在 Pi 上使用 AdafruitIO 设置 MQTT
为了设置 MQTT,我们需要一个代理。虽然 Pi 本身可以充当代理(参见“进一步阅读”部分),但我们不需要在 Pi 上使用代理功能。我们可以使用 AdafruitIO 创建一个代理,我们可以在 Pi 上订阅主题。
创建账户和数据源
首先,我们将前往io.adafruit.com/
并创建一个免费账户。然后,你将被带到仪表板:
我们需要设置一个数据源并获取我们的 AIO 密钥,以便在 Pi 上开始设置。要设置数据源,请选择左侧菜单中的“数据源”。然后,点击左上角的“操作”按钮:
接下来,从下拉菜单中选择“创建新数据源”。给你的数据源命名(我将其命名为hands-on-robotics-with-js
,以便于记住我创建它的目的)。然后,你将被带到新数据源的页面:
现在我们已经创建了我们的数据源,我们可以获取所有需要将 Pi 连接到它的信息。首先,让我们获取用于 MQTT 模块的 Feed 密钥。点击右侧的“Feed 信息”,并复制 MQTT ID 下的值,将其放置在容易获取的地方。然后,关闭“Feed 信息”窗口。
接下来,在左侧菜单中点击“查看 AIO 密钥”。复制活动密钥并将其放置在容易访问的地方。
在我们继续之前,先澄清一些术语:您注意到的以 ID 命名的 MQTT 资源将是我们客户端(树莓派)发送到我们的代理(AdafruitIO)的消息的主题,反之亦然。
我们现在拥有了连接我们的树莓派到 AdafruitIO 代理所需的一切。
使用 mqtt npm 模块订阅资源
为此项目创建一个 project
文件夹,以便将其传输到树莓派。在文件夹内,运行以下命令:
> npm init -y
> npm i --save mqtt dotenv
这将安装 mqtt npm
模块,这将简化我们的 MQTT 连接,以及 dotenv
模块,它允许您在单独的文件中使用环境变量(非常适合确保您不会将 AIO 密钥提交到 GitHub)!
mqtt
模块
mqtt
模块允许我们构建一个 MQTT 客户端对象:
const client = mqtt.connect(url, {options})
我们将通过 options
对象将 AdafruitIO URL、我们的用户名和 AIO 密钥传递给此方法。
在 MQTT 客户端对象上可用的几个事件处理器(见 进一步阅读),但在这个项目中我们将使用以下处理器:
client.on('connect', () => {})
client.on('message', (topic, message) => {})
最后,我们希望能够从我们的客户端向代理发布消息:
client.publish(topic, message)
dotenv
模块
dotenv
模块使得配置您不想提交到 GitHub 的环境变量变得简单。您将其加载到您的 Node.js 应用程序中:
const dotenv = require('dotenv').config()
这将在 Node.js 文件所在的同一目录中加载一个 .env
文件,其格式如下:
KEY=value
然后它可以通过 process.env
全局变量在您的应用程序中访问:
let key = process.env.KEY // 'value'
现在我们对所使用的库和服务有了更多的了解,让我们设置一个测试连接程序!
测试我们的连接
要测试我们的连接,我们将让树莓派连接到 AdafruitIO 代理,订阅我们的新资源,并发布一条消息。当我们在 AdafruitIO 网站上的资源仪表板中看到我们的消息已被接收时,我们就知道它成功了。
要做到这一点,我们需要配置 mqtt
客户端,为客户端设置一个 connect
处理器,并使用该处理器来订阅和发布我们的消息。在您之前设置的文件夹中,创建一个名为 mqtt-test.js
的文件,并写入以下代码:
const dotenv = require('dotenv').config()
const mqtt = require('mqtt')
const client = mqtt.connect(
process.env.ADAFRUIT_IO_URL,
{
username: process.env.ADAFRUIT_IO_USERNAME,
password: process.env.ADAFRUIT_IO_KEY,
port: process.env.ADAFRUIT_IO_PORT
}
)
client.on('connect', () => {
console.log('Connected to AdafruitIO')
client.subscribe(process.env.ADAFRUIT_IO_FEED, () => {
client.publish(process.env.ADAFRUIT_IO_FEED, 'Hello from the Pi!')
})
})
然后,在同一个文件夹中,创建一个名为 .env
的文件(确保它以点开头!),并在其中放置以下内容:
ADAFRUIT_IO_URL=
ADAFRUIT_IO_USERNAME=[the username you signed up for AdafruitIO with]
ADAFRUIT_IO_KEY=[your AIO Key from earlier]
ADAFRUIT_IO_PORT=
ADAFRUIT_IO_FEED=[The feed info from earlier]
然后,将文件夹传输到树莓派上。在树莓派会话中,导航到该文件夹并运行以下命令:
npm i
之前的命令将确保所有模块都正确安装在树莓派上。然后,使用以下命令:
node mqtt-test.js
现在,转到您的 AdafruitIO 资源仪表板。您应该在那里看到一个消息:
现在我们知道我们可以将树莓派连接到 AdafruitIO,让我们添加一个 LCD 来查看传入的消息,以及一个按钮来生成传出消息!
项目 – 添加 LCD 和按钮以查看和发送 MQTT 事件
我们可以使用 AdafruitIO 仪表板将消息发布到我们的 MQTT 馈送,因此我们将使用 LCD 来显示我们发送的内容。我们还将连接一个按钮,当按下时将发送 MQTT 消息。
连接所有部件
首先,我们将我们的 LCD 连接到 I2C 引脚,并将按钮连接到 GPIO #5,也称为 P1-29:
将所有代码整合在一起
在同一文件夹中的文件中创建 mqtt-button-lcd.js
。放入常用的 Johnny-Five 和 Raspi-IO 构造函数,并在板就绪处理程序中:
然后,从 mqtt-test.js
中添加 AdafruitIO 的 MQTT 连接客户端构造函数。我们还将在这里设置我们的 LCD 和按钮对象:
let LCD = new five.LCD({
controller: "PCF8574",
rows: 2,
cols: 16
})
let button = new five.Button('P1-29')
之后,我们就准备好编写在按钮按下时发送消息,以及在 LCD 上打印接收到的消息的代码:
client.on('connect', () => {
console.log('Connected to AdafruitIO')
client.subscribe(process.env.ADAFRUIT_IO_FEED, () => {
client.publish(process.env.ADAFRUIT_IO_FEED, 'Hello from the Pi!')
button.on('press', () => {
client.publish(process.env.ADAFRUIT_IO_FEED, 'Button pressed!')
})
client.on('message', (topic, message) => {
LCD.clear().home().print(topic).setCursor(1,0).print(message)
})
})
})
现在,将文件夹移动到 Pi 上,进入 Pi 会话,导航到文件夹,运行以下命令:
npm i --save johnny-five raspi-io
然后,运行程序(务必使用 sudo
!)
sudo node mqtt-lcd-button.js
现在,按下按钮,你应该会在 AdafruitIO 馈送仪表板上看到消息弹出:
以及你的 LCD(记住,MQTT 事件会发布给所有人,包括发布它们的客户端,如果它们已订阅的话!)
当我们在这里时,点击“操作”,然后添加数据,在数据框中输入“来自 Adafruit 的问候!”并点击创建。它应该会显示在你的 LCD 上:
现在你已经拥有了通过 MQTT 与互联网通信的机器人!
项目 – 使用 IFTTT 的社交媒体通知机器人
在浏览器中打开很多标签页,并点击每个标签以查看通知,可能会很麻烦。幸运的是,我们可以轻松构建一个项目,从多个来源收集通知,创建一个在 LCD 上通知我们的机器人。我们还将了解更多关于 If This, Then That (IFTTT) 及其插件,它将允许我们将事件路由到 Adafruit IO 以及我们的 Pi。
开始使用 IFTTT
IFTTT 是创建图形公式(称为 applet)的一种方式,它由一个触发器(例如社交媒体事件)和一个动作(例如将数据发送到 AdafruitIO)组成。我们将通过将 AdafruitIO 和我们的社交媒体账户链接到 IFTTT,并创建 Applet 将社交媒体通知数据发送到 AdafruitIO。
首先,在 ifttt.com/
上登录或创建一个账户,然后我们将开始链接我们的账户。
将 IFTTT 连接到 Adafruit
要链接你的 AdafruitIO 账户,点击右上角的用户名,选择服务,然后选择页面底部的所有服务链接。然后,在搜索栏中输入 Adafruit
,并点击标记为 Adafruit 的黑色框,应该会带你到这里:
然后,点击连接并输入你的 AdafruitIO 凭据。你希望从任何社交媒体网络接收通知,也应该这样做。我添加了Twitter
和Twitch.tv
账户。
现在我们已经将账户链接,我们应该退回到 Adafruit.IO 并添加我们的社交媒体账户的 feed,这样我们就可以在创建我们的 IFTTT applet 时选择它们。
在 AdafruitIO 中设置社交媒体 MQTT 消息的 feed
在你的 AdafruitIO 仪表板上,你应该为每个你希望你的机器人通知你的社交媒体服务创建一个 feed(这对应于一个 MQTT 主题)。当构建包含多个机器人监听事件的复杂 MQTT 系统时,拥有清晰且粒度化的主题非常重要。
我通过在 AdafruitIO 仪表板的 Feeds 页面选择操作并点击创建新组来为这些 feed 创建了一个组:
我将其命名为Social-Media-Bot
。一旦你完成创建组,点击组名进入该组的仪表板,它应该还没有 feed。点击操作并从下拉菜单中选择创建新 feed。然后,输入你将使用的社交媒体服务的名称作为 feed 的名称。对于你希望使用的任何社交媒体服务重复此操作。
AdafruitIO Feed Group 还充当了一个方便的命名空间工具。Social-Media-Bot 组中的 twitter feed 变成了 Social-Media-Bot.twitter。如果你有多个包含 Twitter 数据 feed 的项目,这将非常有用。
现在我们已经将社交媒体账户链接到 IFTTT,并且 AdafruitIO feed 已经准备好接收数据,让我们创建一些 IFTTT Applets 来收集社交媒体通知。
创建我们的 IFTTT Applets
在 IFTTT 主页上,你通过在右上角选择你的用户名并选择新 Applet 来创建一个新的 applet:
你将看到 IFTTT Applet 公式:
点击+this 链接,进入你可以用来触发你的 IFTTT applet 的服务列表。在搜索栏中填写你希望使用的社交媒体服务的名称,并选择它。
你将看到该服务可能触发的列表。选择你希望被通知的触发器(我从有人关注我的 twitter 账户开始)。一旦你点击触发器,你将返回到公式页面,+this 将被替换为你用作触发器的社交媒体网站的 logo。然后,点击+that 创建我们的操作。
你将被带到类似页面以选择用于操作的服务。搜索AdafruitIO
并选择它。你将被要求填写有关你希望发送到哪个 AdafruitIO feed 的信息,以及你希望发送的消息:
选择与触发此小程序的社交媒体网站匹配的源名称。在要保存的数据中,你可以输入我们可以在 LCD 上显示的消息。你还可以点击添加成分按钮来添加社交媒体事件本身的信息:
我选择了 FullName,最终收到了消息{{FullName}}在 Twitter 上关注了你
!
对第一个社交媒体网站的其他触发器和其他社交媒体网站及其触发器重复此操作。
现在,我们的 IFTTT Applets 正在向 AdafruitIO 发送数据,我们可以开始为我们的机器人布线和编码。
连接我们的项目
对于这个项目,你只需要 LCD;如果你为上一个项目连接了按钮,你可以将其移除:
将我们的社交媒体通知编码到 LCD 上显示
在你的project
文件夹中创建一个名为social-media-bot.js
的文件,并将mqtt-button-lcd
的内容复制进去。我们将修改此文件以创建我们的社交媒体机器人。
首先,删除所有关于按钮变量和Button
对象的引用,因为我们不会使用它们。
接下来,我们需要获取我们将要订阅的源(主题)的名称。为此,点击 AdafruitIO 源页面中你想用于此机器人的每个源。然后在页面右侧,点击源信息并复制 MQTT by key 字段:
然后,在client.on('connect')
处理程序中,我们将订阅多个主题,并且不使用回调功能。然后,我们将添加一个client.on('message')
处理程序来在 LCD 显示器上显示来自 IFTTT 的消息:
client.on('connect', () => {
console.log('Connected to AdafruitIO')
client.subscribe('nodebotanist/feeds/social-media-bot.twitter')
client.subscribe('nodebotanist/feeds/social-media-bot.twitch')
client.on('message', (topic, message) => {
LCD.clear()
LCD.home()
LCD.autoscroll()
LCD.print(message)
})
})
现在,我们已经订阅了新的源并将其设置为打印到 LCD,我们可以运行它!
运行你的社交媒体机器人
运行以下命令以查看输出:
sudo node social-media-bot.js
很快,当你收到社交媒体通知时,你应该会在你的 LCD 上看到一个消息弹出。
摘要
在本章中,我们讨论了几种互联网连接设备之间相互通信的方式。我们深入探讨了 MQTT 协议,并讨论了其 Pub/Sub 接口和抽象使其成为我们 Pi 上的项目的一个很好的选择。然后,我们构建了一个使用按钮与外界通信的机器人,并通过 AdafruitIO 仪表板和 IFTTT 让外界与之通信。最后,我们使用 AdafruitIO 集成到 IFTTT 中构建了一个社交媒体通知机器人。
问题
-
有哪些两种方式物联网设备与互联网接口,但不是特定于物联网的?
-
MQTT 代表什么?
-
MQTT 客户端能做什么?
-
MQTT 代理能做什么?
-
为什么我们的 Pi 会收到它发布到 AdafruitIO 的消息?
进一步阅读
-
关于 MQTT 的优秀教程:
www.pubnub.com/blog/what-is-mqtt-use-cases/
-
MQTT 协议官方站点:
mqtt.org/
-
AdafruitIO 教程:
learn.adafruit.com/category/adafruit-io
-
npm 上的 mqtt 模块页面:
www.npmjs.com/package/mqtt
第十一章:构建 NodeBots 集群
我们现在的 NodeBots 可以感知并显示周围世界的信息,并从互联网收集信息进行显示。现在,是时候看看 NodeBots 之间如何交流了。我们还将利用本章讨论从这里开始的方向——JavaScript 可以在许多不同的板子和设备上使用,以及许多新的和令人兴奋的方式!
本章将涵盖以下主题:
-
项目 – 连接多个 NodeBots
-
扩展你的 NodeBots 知识
-
继续你的 NodeBots 冒险
技术要求
对于本章,你可以选择使用带有无线互联网访问的第二个 Raspberry Pi。你也可以只使用你现有的设备,我们将假装它是两个不同的 Pi。你还需要你的 TSL2591 光传感器、你的 Pi Cobbler 和几根面包板线。
本章的代码可在github.com/PacktPublishing/Hands-On-Robotics-with-JavaScript/tree/master/Chapter11
找到。
项目 – 连接多个 NodeBots
在这个项目中,我们将使用一个npm
模块,它允许我们在 Pi 上设置 Raspberry Pi 代理——如果你有一个 Pi,我们将让它像两个不同的设备一样相互通信,如果你有第二个 Pi,我们将让它们相互通信。
可选 – 设置第二个 Raspberry Pi
如果你只使用你的原始 Pi,请跳过这一部分。
使用第一章中的说明,设置你的开发环境,来设置你的第二个 Pi。你不需要 Cobbler 或其他任何配件来完成这个项目;如果你使用第二个 Pi,你只需要一个良好的电源和一个按照第一章中的说明设置的 microSD 卡,设置你的开发环境。
如果你使用两个 Pi,没有 Cobbler 的 Pi 是代理 Pi,而你原始的带有 Cobbler 的 Pi 是客户端 Pi。
如果你使用两个 Pi,你将想要设置代理 Pi 的主机名,以便它们不会冲突。为此,请运行以下命令:
sudo raspi-config
然后,使用箭头键选择网络选项,然后选择主机名。将主机名设置为你能记住的东西(我使用了nodebotanist-pi-broker.local
)。然后,保存并退出raspi-config
,并重新启动 Pi。
当你想要在代理 Pi 上启动会话时,你现在将使用你自定义的主机名;例如,如果我想 SSH 到我的代理 Pi,我会运行以下命令:
ssh pi@nodebotanist-pi-broker.local
设置你的项目文件和文件夹
你将需要为这个项目创建两个单独的项目文件夹:client
和 broker
。创建这些文件夹并运行以下命令:
npm init -y
在client
文件夹中,运行以下命令:
npm i --save mqtt dotenv
在 broker
文件夹中,运行以下命令:
npm i --save mosca
然后,在两个文件夹中,创建一个index.js
和一个.env
文件。
如果你只有一个 Pi
将client
和broker
文件夹移动到树莓派,在你的树莓派 SSH 会话中导航到client
文件夹,并运行以下命令:
sudo npm i -g forever
npm i --save johnny-five raspi-io
npm i
然后,导航到broker
文件夹并运行以下命令:
npm i
sudo apt-get install mongodb-server // installs mongodb
systemctl enable mongod.service // makes it so mongodb starts when the pi does
然后,重新启动树莓派:
sudo reboot
如果你使用两个树莓派
将client
文件夹移动到你的原始树莓派,通过 SSH 连接到你的原始树莓派,导航到client
文件夹,并运行以下命令:
npm i --save johnny-five raspi-io
npm i
然后,将broker
文件夹移动到你设置的第二个树莓派,通过 SSH 连接到它,导航到broker
文件夹,并运行以下命令:
sudo apt-get install mongodb-server // installs mongodb
systemctl enable mongod.service // makes it so mongodb starts when the pi does
npm i
然后,重新启动代理树莓派:
sudo reboot
现在我们已经安装并配置好了项目依赖,是时候连接这个项目了。
将光传感器添加到树莓派
如果你使用一个树莓派,将光传感器连接到它。如果你使用两个树莓派,将光传感器连接到客户端树莓派。以下图示应与你的单个树莓派或客户端树莓派相匹配:
现在我们可以设置树莓派上的 MQTT 代理。
在树莓派上创建一个 MQTT 代理
如果你使用两个树莓派,请在代理树莓派上执行此整个部分。如果你使用一个树莓派,请在你的一个树莓派上完成所有这些操作。
我们将使用 Mosca 库在树莓派上设置一个 MQTT 代理。mosca npm
库使得设置和启动 MQTT 代理变得非常简单。我们只需要一个运行的 mongoDB 实例(我们在上一步,设置光传感器中已经处理好了)。
在broker
文件夹(在你的原始树莓派或代理树莓派上),在index.js
文件中,我们将设置mosca
:
const mosca = require('mosca')
const mqttBroker = new mosca.Server({
port: 1883,
backend: {
type: 'mongo',
url: 'mongodb://localhost:27017/mqtt',
pubsubCollection: 'MQTT-broker-NodeBots',
mongo: {}
}
})
server.on('ready', () => {
console.log('MQTT broker ready!')
})
server.on('clientConnected', (client) => {
console.log('Client connected to MQTT broker: ', client.id)
})
server.on('published', (packet, client) => {
client = client || {id: 'N/A'} // Catches a weird edge case with mosca
console.log(`Client: ${client.id}\nTopic: ${packet.topic}\nMessage: ${packet.payload.toString()}\n`)
})
我们现在已经准备好了 MQTT 代理!是时候编程我们的客户端了。
编程 MQTT 客户端 – 让树莓派报告家
在你的client
文件夹(以及如果你的设备使用两个树莓派,你的客户端树莓派),打开index.js
文件,编写一个脚本以每次传感器数据变化时收集光传感器数据,阈值为10
(以防止产生过多的 MQTT 消息):
const Raspi = require('raspi-io')
const five = require('johnny-five')
const request = require('request')
const board = new five.Board({
io: new Raspi()
})
board.on('ready', () => {
let light = new five.Light({
controller: 'TSL2591',
threshold: 10
})
light.on('change', () => {
})
})
然后,在board.on('ready')
处理程序之前,构建你的 MQTT 客户端连接,并添加一个mqttClient.on()
处理程序,它订阅light
主题:
const mqttClient = mqtt.connect(
process.env.MQTT_BROKER_URL,
{
port: process.env.MQTT_BROKER_PORT
}
)
mqttClient.on('connect', () => {
mqttClient.subscribe('light')
})
然后,在board.on('ready')
处理程序中,我们将添加将光数据发布到我们的 MQTT 代理的代码。
light.on('change', function() {
mqttClient.publish('light', this.value)
})
现在我们已经编写了客户端代码,我们需要设置环境变量并运行它。
如果你使用一个树莓派
在client
文件夹中,创建一个.env
文件,并添加以下内容:
MQTT_BROKER_URL:mqtt://localhost
MQTT_BROKER_PORT: 1883
确保再次将client
和broker
文件夹移动到你的树莓派上。
如果你使用两个树莓派
在客户端树莓派上,在client
文件夹中,创建一个.env
文件,并添加以下内容:
MQTT_BROKER_URL:mqtt://[broker pi hostname]
MQTT_BROKER_PORT: 1883
将[broker Pi hostname]
替换为你在设置第二个树莓派部分中创建的主机名。
最后一次将client
文件夹移动到客户端树莓派。
现在,是时候运行代码了!
运行我们的 MQTT 项目
对于单个树莓派和两个树莓派的设置,说明略有不同,但最终结果应该是相同的。
如果你使用一个树莓派
通过 SSH 连接到你的树莓派,导航到client
文件夹,并运行以下命令:
su - pi -c "node forever start index.js"
这将使我们的客户端在后台运行,并在必要时重新启动,这样我们就可以看到经纪人的console.log()
输出。它还将确保我们的脚本以 root 用户身份运行,这样 Johnny-Five 代码才能正常工作。
然后,导航到broker
文件夹,并运行以下命令:
sudo node index.js
当你改变传感器上的灯光时,你应该开始在控制台看到经纪人的消息正在移动;这是我们client
文件夹中设置的 MQTT 客户端在与不同端口上的 MQTT 经纪人进行通信。
如果你使用两个树莓派
通过 SSH 连接到你的客户端树莓派,导航到client
文件夹,并运行以下命令:
su - pi -c "node forever start index.js"
这将使我们的客户端在后台运行,并在必要时重新启动,这样我们就可以看到经纪人的console.log()
输出。它还确保我们的脚本以 root 用户身份运行,以便 Johnny-Five 代码能够正常工作。
然后,通过 SSH 连接到你的经纪人树莓派,导航到broker
文件夹,并运行以下命令:
sudo node index.js
当你在传感器上改变灯光时,你应该开始在控制台看到经纪人的消息正在移动;这是两个树莓派正在使用 MQTT 相互通信!你的客户端树莓派正在向经纪人发布消息,它通过console.log()
记录这些消息,但你也可以连接到使用这些数据的其他客户端!
现在,你已经构建了你第一个 NodeBots 集群的开端!现在是时候看看这本书范围之外的 NodeBots 的广阔世界了。
扩展你的 NodeBots 知识
NodeBots 宇宙非常庞大,每天都在扩展!实际上,我们只是刚刚开始探索 Johnny-Five 的世界。
在其他板子上使用 Johnny-Five
在这本书中我们使用的树莓派只是 Johnny-Five 支持的 40 多块板子中的一块。只需浏览一下 Johnny-Five 网站的平台支持页面,就可以清楚地看到这一点:
最棒的消息是,你在这本书中编写的代码可以转移到这里展示的大多数板子上。将你的代码移植到其他板子只需要两个步骤:更改引脚号码,并确保你切换到的板子提供了你需要的功能。
确定引脚号码
改变引脚号码意味着你需要知道你新板上的哪些引脚完成哪些任务。例如,如果你正在将 I2C LCD 从树莓派移动到 Arduino Uno,你需要知道 SDA 和 SCL 引脚在 Uno 上的位置。
获取这些信息最好的方式是在互联网上搜索[板子名称]引脚图,并为 Uno 搜索如下所示的图片:
ARDUINO 板和 ATMega328PU 的引脚图(commons.wikimedia.org/wiki/File:Pinout_of_ARDUINO_Board_and_ATMega328PU.svg
)由 pighixxx 提供,许可协议为Creative Commons Attribution-Share Alike 4.0 International (en.wikipedia.org/wiki/Creative_Commons
)。
然后,你可以从那里匹配引脚。
检查平台支持页面
一些板子支持其他板子不支持的协议和外设:Arduino Uno 有模拟输入引脚,而 Pi 没有,但 Pi 有 USB 支持,而 Uno 没有。幸运的是,Johnny-Five 文档在平台支持部分告诉你什么支持,什么不支持。这里以 Uno 为例:
现在你已经看到了在 Johnny-Five 中继续你的冒险的地方,让我们看看更大的 NodeBots 世界。
其他 Node 机器人平台
在 NodeBots 的世界中,有广阔的天地,而这个列表远非详尽无遗。但让我们迈出第一步,进入这个更广阔的世界。
Tessel 2
Tessel 项目旨在以相对较低的成本创建一个 Node.js 本地的机器人项目,同时提供卓越的用户体验,他们确实做得非常出色(免责声明:我是 Tessel 项目团队的一名贡献成员)。从硬件角度来看,它与 Raspberry Pi 非常相似——它在 Linux 上运行 Node.js;但它不仅仅是一种不同的 Linux,tessel-cli
还抽象化了我们在 Pi 上必须执行的许多ssh-ing
和 Linux 命令。Tessel 2 是当前型号,并且它直接支持 Johnny-Five。要了解更多信息,请访问 Tessel 项目网站tessel.io/
。
使用开源许可从 https://github.com/tessel/project 获取的图片
Espruino 生态系统
此外,还有运行 JavaScript 版本而不是完整 Node.js 的 Johnny-Five 生态系统之外的机器人。一个非常受欢迎的系列是 Espruino 项目板。有 Espruino 主板、Espruino Wi-Fi、Espruino Pico、Puck.js、Pixl.js,以及写作时可用的 MDBT42Q 扩展板。Espruino 项目由 Gordon Williams 领导,你可以在www.adafruit.com/?q=Espruino
找到这些板子,而更详细的信息可在www.espruino.com/
找到。
Espruino 系列在我心中占有特殊位置,因为它曾为我的第一个 NodeBots 供电,一个发光连衣裙:
作者在 NodeConf US 2014 上穿着上图所示的发光连衣裙进行圆桌讨论
使用 Node-RED 进行图形化编程
Node-RED 是一个项目,它允许你使用块进行图形编程,并使用 Node.js 编写块。它的图形界面使许多任务对年轻的未来程序员来说更容易理解:
使用开源许可从 https://github.com/node-red/node-red 获取的图片
再次强调,这个列表只是触及了不断扩大的 NodeBots 世界的表面,我鼓励你们去探索并找到适合你们的方法!
继续你的 NodeBots 冒险
这里有一些我在过去几年中整理的建议,希望能帮助你们继续 NodeBots 之旅;我希望它们能帮到你们,我迫不及待地想看看你们会构建什么!
确定要构建什么
我倾向于在我的手机上打开一个笔记文件,记录我想拥有的酷东西。然后,我会浏览这个清单,思考:我能构建这个吗? 我确保不要考虑是否可以立即直接购买——有时候构建你想要的东西比直接购买更有趣,而且大约 90%的时间,你最终能构建一个适合你的项目,而不是将就使用商店里不完全满足你需求的成品。
与 NodeBots 社区取得联系
加入我们的 Gitter 群组gitter.im/rwaldron/johnny-five
。我们乐于帮助解决问题和回答问题!当你看到有人在 Twitter、Reddit 等地方做 NodeBots 时,确保与他们取得联系!合作带来解决方案和创新,在 Johnny-Five 和 NodeBots 社区中,人越多越有益!
学习更多关于电子的地方
这里有一些书籍的小选集,可以帮助那些不是电气工程师的人学习电子和相关技能;我发现它们在我项目遇到硬件问题时非常有帮助:
-
《Adafruit 优秀焊接指南》(
learn.adafruit.com/adafruit-guide-excellent-soldering/tools
)。如果你不知道如何焊接,或者你只是随意尝试,读一读这本书,确保你的焊接技术不是阻碍你的项目工作的原因。 -
《发明家电子实用指南,第四版》,作者 Paul Scherz。这是一本厚重的书,内容密集,但如果你想学习电子组件的工作原理而不需要做微分方程,这是一本很好的参考书。
-
《电子入门》,作者 Forrest M Mims III。你需要一本比《实用指南》更轻便的书吗?这本书是业余电子爱好者的经典之作,这个项目笔记本系列教你如何使用组件构建有趣且富有教育意义的项目。
摘要
在本章中,我们通过学习如何让我们的 NodeBots 相互交流(或与自己交流)共同完成了 NodeBots 之旅。然后,我们深入了解了 Johnny-Five 和 NodeBots 的更广阔世界。最后,我们推荐了一些优秀的读物,以继续你在电子和相关技能方面的知识探索之旅。
我喜欢看到读者们所创造的成果——请随时通过@nodebotani.st
或@nodebotanist
在推特上联系我,向我展示您的作品——即使它只是一个闪烁的 LED 灯,您只是自豪地想展示它,我也总是很高兴看到。
非常感谢您的阅读。我非常感激我的读者,并祝愿大家一切顺利!
第十二章:评估
第一章
-
我们将通过 Raspbian 发行版使用 Linux 操作系统,并利用它来在 Node.js 中运行我们的项目。
-
GPIO 代表通用输入/输出。
-
Rick Waldron 在 2012 年启动了 Johnny-Five 项目,并编写了一个使用 node-serialport 通过 Node.js 操作 Arduino Uno 的程序。
-
我们在我们的 Pi ssh 会话的命令行上运行
uname -m
命令,以找出 Raspberry Pi 使用的是哪种 ARM 架构。 -
更改默认的 Raspberry Pi 密码很重要,因为默认的用户名和密码并不非常安全,尤其是当你的 Pi 连接到互联网时。
-
Node.js 允许你创建甚至高级的机器人项目,而无需处理任何低级语言,它还具有基于事件的系统和垃圾收集/自动内存管理。
-
Node.js 以创建小型、几乎微小的包而自豪,并拥有出色的 npm 包管理器(及其他工具)来帮助管理这些包。
第二章
-
第一个
LED.strobe()
参数定义了 LED 的闪烁速度,默认为 100 毫秒。如果参数定义为 500,则闪烁将在 500 毫秒的间隔中进行。 -
第二行参数使用
require
函数引入johnny-five
模块。 -
Johnny-Five LED 对象仅作为输出,因此不会发出任何事件。
-
Raspberry Pi 引脚 P1-29 在 GPIO# 方面对应于 GPIO 5。
-
LED.blink()
函数是LED.strobe()
函数的别名。 -
我们从构建一个板对象开始,并将
raspi-io
模块的新实例作为其 I/O 传递。这就是我们告诉 Johnny-Five 库我们正在 Raspberry Pi 上运行代码的方式。
第三章
-
PWM 代表脉冲宽度调制,它设置引脚 HIGH 和 LOW 的时间百分比。对于 LED,它设置有效亮度。
-
Raspberry Pi 3 B+ 有 2 个 PWM 引脚,但它们在同一个通道上运行,实际上创建了一个 PWM 引脚。
-
我们需要 GPIO 扩展器,因为 RGB LED 需要 3 个 PWM 引脚才能完全工作,而 Pi 只有一个内置的。
-
7 种颜色 -- 红色、绿色、蓝色、白色(红+绿+蓝)、黄色(红+绿)、紫色(红+蓝)和青色(绿+蓝)。
-
GPIO 扩展器使用 I2C 协议与 Raspberry Pi 通信。
-
颜色模块接受表示各种格式的颜色的字符串(#FFF、rgb(255,255,255)),并将它们转换为 Pi 和 LED 所理解的红色、绿色和蓝色通道。
-
REPL 通过让你查看和操作机器人的状态来帮助调试。它非常强大,因为大多数机器人平台都有一种在运行时更改代码状态的方法。
第四章
-
Johnny-Five 按钮对象可用的事件是
press
、release
和hold
。 -
Raspberry Pi 不能直接使用模拟输入设备,因为它的所有引脚都是数字的。
-
我们将使用 Pi 上的传感器,这些传感器添加了数字接口,以便访问模拟传感器的读数。
-
Johnny-Five LED.RGB 对象没有事件,因为它严格是输出。
第五章
-
模拟输入传感器从其周围环境接收数据,并将其转换为电压水平表示的值,该值发送到中间处理器或微控制器。
-
模拟传感器不能直接与树莓派接口,因为树莓派的所有 GPIO 引脚都是数字的。
-
我们可以使用两种数字接口将模拟传感器与树莓派连接,即 I2C 和 SPI。
-
一个 I2C 设备需要操作的两个引脚(除了地线和电源之外)是一个 SDA(数据)引脚和一个 SCL(时钟)引脚。
-
传感器对象可以触发
data
事件,这意味着已收集数据,以及change
事件,这表明传感器的数据已更改。 -
barcli 在处理传感器数据时很有帮助,因为你可以通过查看条形图及其在操作传感器时的变化,而不是阅读数百行数字。
第六章
-
电机是一种将电能转换为旋转运动的电气设备。
-
电机和步进电机的区别在于,电机只能被告知移动的方向和速度,而步进电机可以被告知移动多少预定义的增量,这使得它更适合进行精确运动。
-
你应该为电机使用外部电源,否则它将从树莓派中吸取过多的电力,导致在项目运行时出现奇怪的错误或甚至重启。当直接从树莓派供电时,你的电机可能运行缓慢或对命令无反应。
-
我们需要一个树莓派帽子来控制我们的电机,因为要让电机向后移动需要额外的组件,帽子提供了这些组件——它还使得为电机提供外部电源变得更加容易。
-
当控制多个电机时,使用电机对象的好处是能够通过一个命令控制组内所有的电机,同时仍然能够向单个电机发送命令。
第七章
-
伺服电机和电机的区别在于,你可以告诉伺服电机移动到什么角度,而你可以告诉电机向前或向后移动以及速度。
-
定时和连续伺服电机的区别在于,定时伺服电机可以从 0-180 度移动,你可以控制移动到什么角度,而连续伺服电机你只能告诉它旋转的方向和速度,但它有完整的 360 度移动。
-
伺服电机需要一个外部电源,否则它将从树莓派中吸取过多的电力,导致在项目运行时出现奇怪的错误或甚至重启。当直接从树莓派供电时,你的电机可能运行缓慢或对命令无反应。
-
当你想让每次运动都到达一个特定的角度时,你会使用伺服电机而不是电机。
-
Johnny-Five 伺服对象的好处是能够通过一个命令控制组内所有的伺服电机,同时仍然能够向单个伺服电机发送命令。
第八章
-
对于多个伺服电机的复杂运动,动画是必要的,因为这些运动的时机至关重要,而且没有 Easing(缓和)地尽可能快地移动所有伺服电机使得复杂运动几乎不可能实现。
-
关键帧是关于动画在任意时间点伺服电机位置的数组,由动画段定义。
-
提示点是在动画中与关键帧对齐的点。当这些点与动画段中的持续时间结合时,每个关键帧都会得到一个时间。
-
动画段的三部分是关键帧、提示点和持续时间。
-
Easing 通过改变伺服电机从一关键帧移动到另一关键帧时的加速度率来操纵我们的动画关键帧和段。
-
动画对象停止当前段并清除动画队列的方法是
Animation.stop()
。 -
调用
Animation.speed(.25)
将当前运行的动画速度减慢到原始速度的 1/4。
第九章
-
由于 Pi 能够通过 WiFi 或以太网连接到互联网,因此它非常适合需要远程数据的项目。
-
在 Pi 上执行常规 Web 请求时需要考虑的因素是:
-
负载的大小
-
负载解析需要多少 CPU 时间
-
访问的数据是否是打算被访问的
-
WiFi 请求对 Pi 来说消耗大量电力,如果 Pi 没有适当的电源供应可能会引起问题。
-
-
我们可以链式调用 LCD 对象调用,例如
LCD.clear().home()
,因为 Johnny-Five 对象总是返回方法正在工作的对象实例,因此可以在其上调用另一个方法。 -
我们使用 I2C 背板与 LCD 连接,以减少所需的电线和引脚数量从 8 根减少到 2 根,并消除连接自己的电位器来调整 LCD 对比度的需求。
-
如果不使用背板使用 LCD,我们需要更多的组件——我们需要一个电位器来控制 LCD 的对比度。
-
LCD.on()
不会打开整个 LCD,而是打开 LCD 背光。
第十章
-
物联网设备与互联网接口的两种方式,且不特定于物联网的是 HTTP/S 和 WebSockets。
-
MQTT 代表消息队列遥测传输。
-
MQTT 客户端能够连接到代理,订阅主题,处理它们订阅的主题的传入消息,并向主题发布消息。
-
MQTT 代理能够允许 MQTT 客户端连接,确保发布的消息发送到所有订阅了该消息主题的客户端,并且可以向任何主题发布消息。
-
Pi 获取了我们发送到 AdafruitIO 的消息副本,因为它订阅了它发布的主题,并且所有订阅了该主题的设备都会由代理发送消息。