英特尔爱迪生项目-全-

英特尔爱迪生项目(全)

原文:zh.annas-archive.org/md5/f66f5641ba65e03b077ba8749cc88b92

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

英特尔爱迪生项目旨在帮助初学者掌握英特尔爱迪生并探索其功能。英特尔爱迪生是一个嵌入式计算平台,它允许我们探索物联网、嵌入式系统和机器人的领域。

本书将带你了解各种概念,每一章都有一个你可以执行的项目。它涵盖了多个主题,包括传感器数据采集并将其推送到云端以通过互联网控制设备,以及从图像处理到自主和手动机器人的主题。

在每一章中,本书首先介绍该主题的一些理论方面,包括一些小段代码和最小硬件设置。本章的其余部分致力于项目的实践方面。

本书讨论的项目尽可能只需要最少的硬件,并且每个章节的项目都包括在内,以确保你理解基础知识。

本书涵盖的内容

第一章,设置英特尔爱迪生,涵盖了设置英特尔爱迪生的初始步骤,包括刷写和设置开发环境。

第二章,气象站(物联网),介绍了物联网,并使用一个简单的气象站案例,其中我们使用温度、烟雾水平和声音水平并将数据推送到云端进行可视化。

第三章,英特尔爱迪生和物联网(家庭自动化),涵盖了一个家庭自动化的案例,其中我们使用英特尔爱迪生控制电气负载。

第四章,英特尔爱迪生和安全系统,涵盖了英特尔爱迪生的语音和图像处理。

第五章,使用英特尔爱迪生的自主机器人,探讨了机器人领域,其中我们使用英特尔爱迪生和相关算法开发了一条形机器人。

第六章,使用英特尔爱迪生的手动机器人,探讨了无人地面车辆(UGV),并指导你通过开发控制器软件的过程。

你需要为本书准备什么

本书的强制性先决条件是配备 Windows/Linux/Mac OS 的英特尔爱迪生。软件要求如下:

  • Arduino IDE

  • Visual Studio

  • FileZilla

  • Notepad++

  • PuTTY

  • Intel XDK

本书面向的对象

如果你是一名爱好者、机器人工程师、物联网爱好者、程序员或开发者,希望使用英特尔爱迪生创建自主项目,那么这本书适合你。具备先前的编程知识将有所帮助。

术语约定

在本书中,你会发现许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称都按照以下方式显示:“我们可以通过使用include指令来包含其他上下文。”

代码块按照以下方式设置:

int a = analogRead(tempPin ); float R = 1023.0/((float)a)-1.0;
R = 100000.0*R;

float temperature=1.0/(log(R/100000.0)/B+1/298.15)-273.15; Serial.print("temperature = "); Serial.println(temperature);
delay(500);

当我们希望将你的注意力引到代码块的一个特定部分时,相关的行或项目将以粗体显示:

string res = textBox.Text; if(string.IsNullOrEmpty(res))
  {
 MessageBox.Show("No text entered. Please enter again");  }
else
  {
    textBlock.Text = res;

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

npm install mqtt

新术语重要词汇以粗体显示。你会在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:“点击确定后,工具将自动解压缩文件。”

警告或重要注意事项以如下方式显示在框中。

小贴士和技巧看起来像这样。

读者反馈

我们欢迎读者的反馈。告诉我们你对这本书的看法——你喜欢什么或不喜欢什么。读者反馈对我们很重要,因为它帮助我们开发出你真正能从中获得最大价值的标题。

要发送一般反馈,只需发送电子邮件到feedback@packtpub.com,并在邮件的主题中提及书的标题。

如果你在某个领域有专业知识,并且对撰写或参与一本书籍感兴趣,请参阅我们的作者指南www.packtpub.com/authors

客户支持

现在你已经是 Packt 图书的骄傲拥有者,我们有一些事情可以帮助你从购买中获得最大价值。

下载示例代码

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

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

  1. 使用你的电子邮件地址和密码登录或注册我们的网站。

  2. 将鼠标指针悬停在顶部的“支持”选项卡上。

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

  4. 在搜索框中输入书的名称。

  5. 选择你想要下载代码文件的书籍。

  6. 从下拉菜单中选择你购买这本书的地方。

  7. 点击“代码下载”。

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

  • WinRAR / 7-Zip for Windows

  • Zipeg / iZip / UnRarX for Mac

  • 7-Zip / PeaZip for Linux

这本书的代码包也托管在 GitHub 上github.com/PacktPublishing/Intel-Edison-Projects。我们还有来自我们丰富的图书和视频目录的其他代码包可供选择,在github.com/PacktPublishing/。查看它们吧!

下载这本书的颜色图像

我们还为您提供了一个包含本书中使用的截图/图表的颜色图像的 PDF 文件。这些彩色图像将帮助您更好地理解输出的变化。您可以从www.packtpub.com/sites/default/files/downloads/IntelEdisonProjects_ColorImages.pdf下载此文件。

勘误

尽管我们已经尽最大努力确保内容的准确性,错误仍然可能发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一情况,我们将不胜感激。通过这样做,您可以避免其他读者感到沮丧,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。

要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分下。

盗版

互联网上版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现我们作品的任何非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。

请通过copyright@packtpub.com与我们联系,并提供涉嫌盗版材料的链接。

我们感谢您在保护我们的作者和我们为您提供有价值内容的能力方面的帮助。

询问

如果您对本书的任何方面有问题,您可以通过questions@packtpub.com与我们联系,我们将尽力解决问题。

第一章:设置英特尔 Edison

在每个物联网IoT)或机器人项目中,我们都有一个控制器,它是整个系统的核心。同样,我们也有英特尔 Edison。英特尔 Edison 计算模块有两种不同的包装:一个是迷你断开板;另一个是 Arduino 兼容板。您可以使用板的原生状态,但在这种情况下,我们必须自己制作扩展板。Edison 基本上是 SD 卡的大小。由于其小巧的尺寸,它非常适合可穿戴设备。然而,它的功能使其适合物联网应用;更重要的是,强大的处理能力使其适合机器人应用。然而,我们并不简单地使用这种状态的设备。我们将板与扩展板连接起来。扩展板为用户提供足够的灵活性和兼容性,以便与其他单元接口。Edison 有一个操作系统,它运行整个系统。它运行 Linux 镜像。因此,要设置您的设备,您最初需要在硬件和软件级别进行配置。

在本章中,我们将介绍以下主题:

  • 设置英特尔® Edison

  • 设置开发者环境

  • 使用 Arduino IDE、Intel XDK 和其他工具在板上运行示例程序

  • 通过我们的 PC 与主板交互

初始硬件设置

我们将专注于随 Arduino 扩展板一起提供的 Edison 软件包。最初,您将获得两个不同的部件:

  • 英特尔® Edison 主板

  • Arduino 扩展板

下图显示了设备的架构:

英特尔 Edison 的架构。图片来源:www.software.intel.com

我们需要将这两部分连接成一个单元。将 Edison 主板放在扩展板上,以便 GPIO 接口在一点上相遇。轻轻推压 Edison,您会听到一声点击。使用随包装提供的螺丝来紧固设置。一旦完成,我们现在将在硬件和软件级别设置设备以供进一步使用。以下是我们将详细介绍的步骤:

  1. 下载必要的软件包

  2. 将您的英特尔® Edison 连接到您的 PC

  3. 使用 Linux 镜像刷写您的设备

  4. 连接到 Wi-Fi 网络

  5. SSH 您的英特尔® Edison 设备

下载必要的软件包

要在这个平台上进行开发,我们需要下载并安装一些软件包,包括驱动程序和 IDE。以下是需要软件及其链接的列表:

驱动程序和其他下载

驱动程序和其他杂项可以从以下网址下载:

可以从software.intel.com/en-us/iot/hardware/edison/downloads下载第一个和第二个软件包。

插入您的设备

软件和驱动程序安装完成后,我们将把设备连接到电脑。您需要两条 Micro-B USB 线来连接您的设备到电脑。您也可以使用 9V 电源适配器和一条 Micro-B USB 线,但现阶段我们不会使用电源适配器。电源适配器的主要用途将在本书的后续章节中介绍,特别是当我们需要与需要 USB 接口的其他设备交互时。以下图显示了英特尔爱迪生 Arduino 扩展板的各个部分:

英特尔爱迪生 Arduino 扩展板的各个部分

在 USB 端口和 OTG 端口之间存在一个小开关。这个开关必须指向 OTG 端口,因为我们将从 OTG 端口为设备供电,而不是通过直流电源端口供电。一旦连接到您的电脑,打开设备管理器并展开端口部分。如果所有驱动程序的安装都成功了,您将看到两个端口:

  • 英特尔爱迪生虚拟串行端口

  • USB 串行端口

烧录您的设备

一旦您的设备成功检测并安装,您需要使用 Linux 镜像对设备进行烧录。为此,我们将使用英特尔提供的闪存工具:

  1. 打开闪存轻量级工具并将您的设备连接到电脑:

英特尔手机闪存轻量级工具

  1. 打开闪存工具后,点击“浏览...”并浏览到您下载的 Linux 镜像的.zip文件。

  2. 点击“确定”后,工具将自动解压文件。

  3. 接下来,点击“开始”以进行烧录:

Intel® Phone flash lite tool — stage 1

  1. 您将被要求断开并重新连接您的设备。这样做,板子应该开始烧录。烧录完成可能需要一些时间。在此过程中不要干扰设备。

  2. 烧录完成后,我们可以配置设备:

Intel® Phone flash lite 工具 — 完成

配置设备

焊接成功后,我们现在将配置设备。我们将使用 PuTTY 控制台进行配置。PuTTY 最初由 Simon Tatham 为 Windows 平台开发的一个 SSH 和 telnet 客户端。我们将在这里使用串行部分。

在打开 PuTTY 控制台之前,打开设备管理器并记下 USB 串行端口的端口号。这将在你的 PuTTY 控制台中使用:

Intel® Edison 在 PuTTY 中的端口

接下来,在 PuTTY 控制台中选择串行,并输入端口号。使用波特率为115,200。按Open打开与设备通信的窗口:

PuTTY 控制台 — 登录界面

一旦你进入 PuTTY 控制台,你可以执行命令来配置你的 Edison。以下是我们将在控制台中执行的任务集以配置设备:

  1. 为你的设备提供一个名称。

  2. 提供一个 root 密码(SSH 你的设备)。

  3. 将你的设备连接到 Wi-Fi。

初始时,在控制台中,你会被要求登录。输入root并按Enter键。你会看到 root@edison,这意味着你已经在root目录下:

PuTTY 控制台 — 登录成功

现在,我们进入了设备的 Linux 终端。首先,我们将输入以下命令进行设置:

configure_edison -setup

输入命令后按Enter键,整个配置将非常直接:

PuTTY 控制台 — 设置密码

首先,你会被要求设置一个密码。输入一个密码并按Enter键。你需要再次输入你的密码以进行确认。接下来,我们将为设备设置一个名称:

PuTTY 控制台 — 设置名称

为你的设备提供一个名称。请注意,这并不是你的设备的登录名。它只是你的设备的别名。此外,名称至少应该有五个字符长。一旦你输入了名称,它会要求你确认:按y进行确认。然后它会要求你设置 Wi-Fi。再次选择y继续。设置 Wi-Fi 不是强制性的,但建议这样做。我们需要 Wi-Fi 进行文件传输、下载软件包等:

PuTTY 控制台 — 设置 Wi-Fi

一旦扫描完成,我们会得到一个可用网络列表。选择与你的网络对应的数字并按Enter键。在这种情况下,是5,对应 avirup171,这是我使用的 Wi-Fi。输入网络凭据。完成这些操作后,你的设备将连接到 Wi-Fi。设备连接后,你应该会得到一个 IP 地址:

PuTTY 控制台 — 设置 Wi-Fi -2

连接成功后,你应该会看到这个屏幕。确保你的 PC 连接到相同的网络。在你的 PC 上打开浏览器,并输入控制台中显示的 IP 地址。你应该会看到一个类似的屏幕:

Wi-Fi 设置 — 完成

现在,我们已经完成了初始设置。然而,Wi-Fi 设置通常不会一次性完成。有时您的设备无法连接到 Wi-Fi,有时我们无法获取之前显示的页面。在这些情况下,您需要启动 wpa_cli 来手动配置 Wi-Fi。

有关详细信息,请参阅以下链接:

英特尔支持

Wi-Fi 设置完成后,我们可以继续设置我们的开发环境。我们将介绍以下编程语言及其相应的 IDE:

  • Arduino 处理器语言(C/C++)

  • Python

  • Node.js

Arduino IDE

Arduino IDE 是一个著名且广泛使用的集成开发环境,它不仅涵盖了 Arduino 板,还包括英特尔的其他许多板,如 Galileo、Edison、Node MCU 等。该语言基于 C/C++。一旦您从本章开头提到的链接下载了 Arduino IDE,您可能不会收到 Edison 板的软件包。我们需要从 IDE 本身手动下载该软件包。为此,打开您的 Arduino IDE,然后转到工具 | 板: "Arduino/Genuino Uno" | 板管理器...:

图片

Arduino IDE

您现在需要点击板管理器并选择英特尔 i686 板。点击版本号,然后点击安装。板管理器是 IDE 的一个极其重要的组件。我们使用板管理器来添加外部兼容 Arduino 的板:

图片

板管理器

一旦安装,您应该会在工具 | 板下看到您的板:

图片

板安装成功

一旦成功安装,您现在将能够使用 IDE 编程设备。像每个入门程序一样,我们还将烧录一个简单的程序到英特尔 Edison,使其以我们设定的间隔闪烁板上的 LED。通过这种方式,使用 Arduino IDE 的程序基本结构也将变得清晰。当我们最初打开 IDE 时,我们得到两个函数:

  • void setup()

  • void loop()

设置函数是我们声明引脚是否要配置为输出模式或输入模式的地方。我们还在设置方法中启动各种其他服务,例如串行端口通信。根据用例,实现方式会有所不同。循环方法是指代码在无限序列中重复执行的段。我们的主要逻辑在这里。现在我们需要以 1 秒的间隔闪烁一个 LED:

#define LED_PIN 13 
void setup() 
  { 
    pinMode(LED_PIN, OUTPUT); 
  } 

void loop() 
  { 
    digitalWrite(LED_PIN, HIGH); 
    delay(1000); 
    digitalWrite(LED_PIN, LOW); 
    delay(1000); 
  } 

在前面的代码中,行#define LED_PIN 13是一个用于定义 LED 引脚的宏。在 Arduino 扩展板上,一个 LED 和一个电阻已经连接到pin 13,因此我们现在不需要连接任何额外的 LED。在setup函数中,我们使用pinMode函数和两个参数定义了引脚的配置为输出。在loop函数中,我们最初使用digitalWrite函数和两个参数将引脚设置为高,然后定义了一个 1,000 毫秒的延迟,相当于 1 秒。延迟后,我们将引脚设置为低,然后再次定义一个 1 秒的延迟。前面的代码解释了在 Arduino IDE 中编写的 Arduino 代码的基本结构。

要将此程序烧录到 Edison 设备上,首先使用编译按钮编译代码,然后选择设备的端口号,最后点击上传按钮上传代码:

图片

Arduino IDE — 闪烁

端口号可以在“工具 | 端口”下选择。

现在我们知道了如何使用 Arduino 编程,让我们看看它实际上是如何工作的,或者 Arduino IDE 内部发生了什么。

在上传代码的过程中,实际上会发生一系列步骤:

  1. 首先,Arduino 环境执行一些小的转换,以确保代码是正确的 C 或 C++(两种常见的编程语言)。

  2. 接着,它被传递给一个编译器(avr-gcc),将可读性代码转换为机器可读指令(或目标文件)。

  3. 然后,你的代码将与标准 Arduino 库(链接到)结合,这些库提供了基本功能,例如digitalWrite()Serial.print()。结果是单个 Intel hex 文件,其中包含需要写入 Arduino 板上芯片程序内存的特定字节。

  4. 然后,该文件被上传到板子上,通过 USB 或串行连接通过芯片上已存在的引导加载程序或外部编程硬件进行传输。

Python

Edison 也可以用 Python 编程。代码需要在设备上直接运行。我们可以直接使用任何编辑器(如 VI 编辑器)编程设备,或者首先在 PC 上编写代码,然后使用任何 FTP 客户端(如 FileZilla)传输。这里我们首先使用 Notepad++编写代码,然后传输脚本。这里我们也将执行一个简单的脚本,该脚本将使板载 LED 闪烁。在处理 Python 和硬件时,我们需要使用 MRAA 库来与 GPIO 引脚接口。这是一个用于 GNU/Linux 平台通信的低级骨架库。它支持几乎所有广泛使用的基于 Linux 的板子。因此,最初您需要在板上安装这个库。

打开 PuTTY 并登录到您的设备。登录后,我们将添加 AlexT 的非官方opkg仓库。

要做到这一点,请使用 VI 编辑器将以下行添加到/etc/opkg/base-feeds.conf中:

src/gz all http://repo.opkg.net/edison/repo/all
src/gz edison http://repo.opkg.net/edison/repo/edison src/gz core2-32 http://repo.opkg.net/edison/repo/core2-32

接下来,通过执行以下命令更新包管理器并安装 git:

opkg update
opkg install git  

我们将从 GitHub 克隆 Edison-scripts 以简化某些事情:

git clone https://github.com/drejkim/edison-scripts.git ~/edison-scripts

接下来,我们将~/edison-scripts添加到路径中:

echo 'export PATH=$PATH:~/edison-scripts' >> ~/.profile
source ~/.profile

现在,我们将运行以下脚本以完成此过程。请注意,前面的步骤不仅会为 MRAA 配置设备,还会为本书中的后续项目设置环境。

首先,运行以下脚本。只需输入:

resizeBoot.sh
Then go for 
installPip.sh

之前的包是 Python 包管理器。这将在本书的后续部分中用于安装必要的 Python 包。最后,我们将通过执行以下命令安装 Mraa:

installMraa.sh

MRAA 是 GNU/Linux 平台上的低级骨架库,用于通信。Libmraa是一个 C/C++库,具有与 Java、Python 和 JavaScript 的绑定,用于与 Galileo、Edison 和其他平台上的 IO 接口。简单来说,它允许我们在 IO 引脚上操作。

完成前面的步骤后,我们可以开始编写 Python 代码。为此,打开任何代码编辑器,例如 Notepad++,并输入以下代码:

import mraa 
import time 

led = mraa.Gpio(13)
led.dir(mraa.DIR_OUT)

while True:
        led.write(1)
        time.sleep(0.2)
        led.write(0)
        time.sleep(0.2) 

请将前面的代码保存为.py扩展名,例如blink.py,现在,我们将逐行解释它。

初始时,使用导入语句,我们导入两个库:MRAA 和 time。MRAA 是用于与 GPIO 引脚接口的底层骨架库:

led = mraa.Gpio(13)
led.dir(mraa.DIR_OUT)

在这里,我们初始化 LED 引脚并将其设置为输出模式:

while True:
        led.write(1)
        time.sleep(0.2)
        led.write(0)
        time.sleep(0.2) 

在前面的块中,我们将我们的主要逻辑放在一个无限循环块中。现在,我们将将其传输到我们的设备。为此再次进入 PuTTY 控制台,并输入ifconfig。在wlan0部分下,记下你的 IP 地址:

要使用的 IP 地址

现在打开 FileZilla 并输入你的凭据。确保你的设备和你的 PC 在同一个网络中:

  • 主机:根据前面的截图获取的 IP 地址:192.168.0.101

  • 用户名:root,因为你将登录到根目录

  • 密码:你的 Edison 密码

  • 端口:22

输入后,你将获得设备的文件夹结构。现在,我们将从我们的 PC 将 Python 代码传输到设备。为此,只需在 Windows 资源管理器中找到你的.py文件,并将其拖放到 FileZilla 控制台 Edison 文件夹中。目前,只需将文件粘贴到root目录下。一旦这样做并且成功,通过访问 PuTTY 控制台并执行ls命令,文件应该在你的 Edison 设备中可见。

另一种选择是在 FileZilla 的左侧找到你的文件;一旦找到,只需右键单击文件并点击上传。以下是 FileZilla 窗口的典型截图:

FileZilla 应用程序

一旦传输并成功使用ls命令列出,我们将运行脚本。要运行脚本,在 PuTTY 控制台中,转到你的root目录,并输入以下命令:

python blink.py

如果文件存在,那么您应该会在设备上看到 LED 闪烁。恭喜!您已经在 Edison 板上成功编写了 Python 脚本。

Intel XDK for IoT (Node.js)

我们还将介绍另一个 IDE,这是英特尔强大的跨平台开发工具:Intel XDK。这将用于运行我们的 Node.js 脚本。理想情况下,我们从 XDK 运行 Node.js 脚本,但始终有选项通过使用 FTP 客户端(如 FileZilla)将.js文件传输到您的设备,并使用node FileName.js来运行您的脚本。从本章开头提供的下载软件列表中下载并安装 XDK,然后打开它。您可能需要登录到英特尔开发者区。完成后,打开您的 XDK。然后,在物联网嵌入式应用程序下,选择一个空白物联网 Node.js 模板:

XDK 截图

一旦打开,将所有现有代码替换为以下代码:

var m = require('mraa'); //require mraa 
console.log('MRAA Version: ' + m.getVersion()); //write the mraa version to the console 

varmyLed = new m.Gpio(13); //LED hooked up to digital pin 13 (or built in pin on Galileo Gen1 & Gen2 or Edison) 
myLed.dir(m.DIR_OUT); //set the gpio direction to output 
varledState = true; //Boolean to hold the state of Led 

functionperiodicActivity() 
  { 
    myLed.write(ledState?1:0);
    ledState = !ledState; 
    setTimeout(periodicActivity,1000); 
  }
periodicActivity(); //call the periodicActivity function 

如果您仔细查看代码,可能会注意到代码的结构与其他两个平台的大致相似。我们最初导入MRAA库:

var m = require('mraa');
console.log('MRAA Version: ' + m.getVersion());  

我们还显示安装的MRAA版本(您可以跳过此步骤)。下一个任务是初始化和配置引脚为输出或输入模式:

varmyLed = new m.Gpio(13); 
myLed.dir(m.DIR_OUT); 
varledState = true;

我们使用ledState来获取 LED 的当前状态。接下来,我们定义一个单独的函数来闪烁逻辑:

functionperiodicActivity() 
  { 
    myLed.write(ledState?1:0);
    ledState = !ledState; 
    setTimeout(periodicActivity,1000); 
  } 
periodicActivity(); 

最后,我们调用函数。仔细检查代码,很明显我们只使用了一个毫秒级的延迟,因为我们使用三元运算符检查当前状态。为了在设备上执行代码,我们首先需要连接我们的设备。

要将您的设备连接到 XDK,请转到物联网设备部分,然后点击下拉菜单。您可能会在下拉菜单中看到您的设备。如果您看到了,请点击连接:

XDK 截图 — 连接面板

如果设备未列出,则需要添加手动连接。点击添加手动连接,然后添加凭证:

手动连接截图

在地址中,输入 FileZilla 中使用的 IP 地址。在用户名中插入root,密码是之前设置的密码。点击连接,您的设备应该会连接。点击上传以上传程序,点击运行以运行程序:

上传和执行代码截图

上传后,连接到13引脚的 LED 应该闪烁。通常,在处理复杂项目时,我们选择空白模板,以便更容易自定义并完成所需的工作。

更多示例和 XDK 的详细信息可在:software.intel.com/en-us/getting-started-with-xdk-and-iot

摘要

在本章中,我们介绍了英特尔爱迪生的初始设置以及如何将其配置到网络中。我们还探讨了如何将文件传输到爱迪生以及从爱迪生传输文件,并设置了 Arduino、Python 和 Node.js 的开发环境。我们进行了一些示例编程,例如闪烁 LED 灯,使用了这三个平台。通过这个过程,我们对操作爱迪生和开发简单到复杂的项目有了相当的了解。

在第二章《气象站(物联网)》,我们将构建一个迷你气象站,并能够将项目部署在英特尔爱迪生(Intel Edison)上。

第二章:气象站(物联网)

在第一章 设置英特尔爱迪生 中,我们学习了如何设置英特尔爱迪生并在各种语言中运行一些基本程序。在本章中,我们将事情扩展一点,将互联网引入物联网的画面。我们将经常听到这个术语,但还没有确切的定义。然而,如果我们尝试提取物联网的字面意义,它意味着将设备连接到互联网。它也可能被称为连接设备智能设备。它是机器对机器M2M)通信的高级形式。在本章中,主要关注物联网的架构以及英特尔爱迪生是如何成为围绕物联网开发系统的完美选择的。我们还将处理典型物联网项目的各种组件以及如何在组件级别进行扩展。到本章结束时,我们将构建一个使用英特尔爱迪生的系统,该系统将接收污染数据、温度数据和声音数据,并将其上传到网络并在仪表板上实时显示。

在本章中,我们将涵盖以下主题:

  • 典型物联网系统架构,将传感器与英特尔爱迪生接口

  • 连接设备并将数据上传到云端,部署带有英特尔爱迪生的微型气象站

物联网及其用法的概述

物联网有很多用途,可以使普通事物变得智能化。从工业开始,我们处理大型机械,如工业机器人和装配线工业单元,物联网的根源植根于许多用途,为云端或本地服务器提供关键数据,我们可以在任何使用物联网的地方远程监控并提供远程功能。现在想象一个医疗保健解决方案,我们家庭中有年迈的成员,我们需要定期监控他们。

智能医疗设备出现在画面中,从人体收集的数据不断推送到云端,并在本地层面监控任何异常迹象。在云端进行适当的分析,利用机器学习算法,我们可以预测个人的健康状况。考虑到将在第三章中讨论的智能家居自动化用例,英特尔爱迪生和物联网(家庭自动化),我们可以远程控制设备并监控如灯光、风扇和空调等电气负载的用法,无论在世界任何地方。

典型物联网项目的架构

在处理物联网项目时,有一些关键因素应该牢记在心,以下是一些:

  • 硬件选择

  • 网络协议的选择

  • 传感器的选择

  • 物联网平台的选择

除了之前提到的点之外,还有一些其他因素将在本章后面的部分变得清晰。在处理物联网时,我们首先想到的是上传数据。通常,物联网项目涉及从传感器读取数据并将其上传到网络。在这个过程中,包括数据处理和过滤、分析和机器学习在内的几个子过程都会发挥作用。

以下图示展示了一个标准架构:

图片

物联网架构概述

如前图所示,典型的物联网系统可能包括以下组件:

  • 传感器

  • 控制器执行器

  • 物联网平台或云

  • 仪表盘和警报

传感器

传感器在物联网空间中扮演着关键角色。它们充当物理世界和数字世界之间的一种连接层。传感器耦合提供了我们想要的整个系统的数据。目前我们并不专注于工业级传感器,但低功耗传感器适合小型概念验证。在处理传感器时,应考虑以下因素:

  • 传感器使用的电源

  • 提供的输出类型(数字或模拟)

  • 传感器的灵敏度

传感器通常与控制器进行单向通信。传感器捕获的数据通过 ADC 转换为数字值,我们通过控制器获得输出。让我们考虑一个光传感器的用例。在这种情况下,传感器返回一个校准后的电阻值。因此,我们根据光强度得到一个电阻值。其他传感器的情况类似,任何物理组件都被读取并转换为数字组件。通常,传感器有三个引脚;Vcc、Gnd 和信号。传感器与控制器的接口将在本章后面讨论。

控制器

这些是物联网系统的“大脑”。控制器在物联网空间中执行大部分操作。在工业领域,控制器可能被称为物联网网关。从数据采集到处理这些数据,再到将其上传到云并控制执行器,所有这些操作都是由控制器完成的。通常,控制器具有网络和无线通信的能力。如果某些控制器没有这种能力,那么通常它们会与外部网络设备堆叠。Arduino、Raspberry Pi、Intel Galileo 和 Intel Edison 是最常用的控制器之一。控制器通常带有 GPIO 引脚,用于与其他设备接口。我们使用的控制器 Intel Edison 的大部分细节在第一章,设置 Intel Edison中已有讨论。

执行器

执行器主要由提供基于控制器信号的某些动作的机电装置组成。基于控制器发送的信号,执行器被激活或去激活。执行器主要是电机;伺服电机也属于这一类别。由于执行器的高功率需求或电流的性质(交流或直流),控制器不能直接控制执行器。接口的硬件组件称为驱动电路。

控制器向驱动电路发送控制信号,根据这些控制信号,执行器被激活。在处理本书的机器人模块时,我们将详细讨论执行器。

云或物联网平台

这可能是整个物联网(IoT)生态系统中最重要的一部分,因为最终我们需要上传数据,以便可以从任何地方访问,这是物联网的互联网部分。通常我们更倾向于选择物联网平台。这些平台都有自己的 SDK,我们只需使用这些 SDK 将我们的设备与物联网平台配对并上传数据。物联网平台中的顶级玩家包括微软 Windows Azure、IBM Bluemix 和亚马逊网络服务。还有其他提供这些服务的平台,例如 Datonis、Dweet、Thingspeak、Thingworx 等等。物联网平台的选择非常具体,取决于某些因素,例如:

  • 支持的协议:这些主要是 REST、WebSockets 和 MQTT

  • 仪表板功能:平台拥有自己的仪表板用于可视化,以及开发自定义仪表板的灵活性

  • 规则引擎:这些包括基于传入数据需要定义的规则

  • 基于事件的 服务:这些是基于规则引擎输出的触发事件的必要性

在本章中,我们将讨论在我们的项目中使用dweet.io,一个迷你气象站。

仪表板和警报

当我们有数据时,数据应以仪表盘、图表等形式呈现。在大多数情况下,平台本身提供支持,但有时会出现根据我们获得的数据构建自定义仪表板的需求。通常通过 REST API 调用,我们从物联网平台获取数据到我们的自定义仪表板。这些也是物联网生态系统的重要组成部分。仪表板还必须与从 PC 到移动设备等各种设备兼容。它们也必须是实时的。虽然仪表板处理数据可视化,但另一方面,警报负责通知用户系统中的任何故障或异常。通常警报的首选方式是某些可视化、推送通知、电子邮件、短信等等。

将传感器与英特尔爱迪生(Intel Edison)接口

在本章前面,我们简要地了解了一些传感器。现在我们将看到如何将这些传感器与英特尔爱迪生板进行接口。让我们考虑使用温度传感器的例子。如前所述,大多数传感器都有三或四引脚配置:

  • Vcc

  • 地线

  • 信号

如果你查看爱迪生板,板上会有模拟引脚。理想情况下,如果传感器返回一个模拟值,那么它就会进入模拟引脚。对于数字输出也是类似的:我们更倾向于使用数字引脚。让我们看看以下示例,我们将要连接一个温度传感器。通常,一个典型的温度传感器有三个引脚。配置与之前相同。然而,有时由于板兼容性问题,它可能带有四引脚配置,但在那种情况下,其中一个引脚是不使用的。

在这个例子中,我们将使用一个 Grove 温度传感器模块:

图片

Grove 温度传感器模块

上一张图片是一个温度传感器。你可能注意到它有四个引脚,分别标记为 Vcc、Gnd、Sig 和 NC。为了与你的爱迪生板连接,请遵循以下电路图:

图片

基本温度数据电路图

在这个电路图中,我们可以注意到 NC 引脚没有连接。因此,只有三个引脚,即 Vcc、Gnd 和 Sig,被连接。现在,一旦你完成了连接,我们需要编写一些读取数据的算法。标准程序是查找传感器的数据表。通常,我们也会得到传感器获取所需参数的数学方程。

对于 Grove 温度传感器模块,我们的第一个目标是获取制造商网站上的某些数据。通常,这些传感器根据电阻的变化来计算温度:

图片

温度计算算法。图片来源:http😕/wiki.seeed.cc/Grove-Temperature_Sensor_V1.2/

最终公式如下:

B=4275

R0=100000

R= 1023.0/a-1.0 R= 100000R*

最终温度 = 1.0/(log(R/100000.0)/B+1/298.15)-273.15

前面的推导来自http😕/wiki.seed.cc/Grove-Temperature_Sensor_V1.2/.

当将前面的推导转换为 Arduino IDE 的代码以部署到 Edison 时,结果如下:

#include <math.h> 

constint B=4275; constint R0 = 100000; constint tempPin = A0; void setup() 

  { 

    Serial.begin(9600); 

  } 

void loop() 

  { 

    int a = analogRead(tempPin ); float R = 1023.0/((float)a)-1.0; 
    R = 100000.0*R; 

    float temperature=1.0/(log(R/100000.0)/B+1/298.15)-273.15;
    Serial.print("temperature = "); Serial.println(temperature); 
    delay(500); 

  } 

代码说明

让我们讨论我们需要遵循的步骤:

  1. 初始时,我们声明BR0的值。这些值来自数据表,如算法中所示。

  2. 接下来,我们声明将使用的模拟引脚,tempPin

  3. setup()函数中,我们只是执行Serial.begin(9600)操作。问题是,我们没有将tempPin设置为输入模式,因为默认情况下模拟引脚是输入模式。

  4. 在循环的下一个步骤中,我们将实现之前在代码中执行的运算,并在串行监视器上显示它。

  5. 要访问串行监视器,请按右上角按钮。串行窗口打开后,您将看到您当前房间的温度:

图片

串行窗口。注意波特率和温度读数

从前面的屏幕截图,您可以注意到的温度读数,这非常接近原始温度。根据灵敏度,温度可能会有所不同。

现在我们知道了如何访问温度传感器的读数,我们可以继续进行其他传感器的集成,以完成我们的气象站项目。

连接设备并将数据上传到云端(dweet.io)

现在我们知道了如何读取传感器的数据,我们的下一个目标是选择一个物联网平台,我们将把数据上传到该平台。我们之前简要讨论过物联网平台。

在本节中,我们将处理dweet.io。这个物联网平台非常易于实现。我们将使用dweet.io的 Node.js SDK。在深入了解之前,让我们看看这个平台。我们的目标是把我们获取的温度数据推送到平台,并在仪表板上显示。

dweet.io是一个简单的机器(传感器、机器人、设备等)发布和订阅服务。它就像 Twitter,但针对事物。每个事物都分配了一个唯一的名称,通过 REST 服务,我们可以访问和更新它们。需要注意的是,我们创建的事物是公开的。为了创建一个私有事物,我们需要付费。在这里,我们将仅介绍其公开方面:

关于dweet.io的详细信息,请参阅以下链接:

dweet.io/faq

  1. 这将是我们的气象站的第一步之一,因为温度是其不可或缺的一部分:

图片

Dweet.io 截图

  1. 现在,点击播放标签进入创建区域:

图片

播放—dweet.io

  1. 接下来,我们将为我们的事物提供一个名称:

图片

dweet.io—创建事物

  1. 为您的“事物”命名。名称必须是唯一的。

  2. 点击“试试看!”按钮后,您将获得一个请求 URL。

  3. 现在,点击https://dweet.io/follow/EdisonTemperature浏览到您的公共事物页面。一旦 Edison 连接,我们就会在这里接收数据。所以,现在,让我们将一个温度传感器连接到 Edison,并将其连接到您的电脑。

  4. 在这个迷你项目中,我们将编写一个 Node.js 程序,该程序将访问传感器的数据并将其上传到前面链接的此处。此外,我们不会将代码写入 Intel XDK,而是在 Notepad++中编写代码,并使用 FileZilla 客户端进行传输。

  5. 但再次,我们还没有访问到这些库。因此,我们需要将这些库添加到我们的 Edison 设备中。为此,启动您的 PuTTY 终端并登录到您的 Edison:

PuTTY console-setup—1

  1. 接下来,使用ifconfig命令检查您的设备是否已连接到 Wi-Fi 网络:

PuTTYconsole-setup—2

  1. 接下来,我们将在我们的设备上安装dweet.io的 Node.js SDK。执行以下命令:
 npm install node-dweetio -save

根据您的互联网连接速度,模块安装可能需要一些时间。警告可以忽略。

  1. 安装完成后,我们将为 Edison 编写 Node.js 脚本。现在打开您的编辑器并输入以下代码:
      function dweetSend()
        {
          vardweetClient = require("node-dweetio"); vardweetio =
          newdweetClient();
          var mraa= require('mraa'); var B=4275;
          var R0=100000;
          var tempPin=new mraa.Aio(0); var a=tempPin.read();
          var R=1023/a-1; R=100000*R;
          var temperature=1/(Math.log(R/100000)/B+1/298.15)-273.15;
          temperature = +temperature.toFixed(2);
          dweetio.dweet_for("WeatherStation",
          {Temperature:temperature}, function(err, dweet)
            {
              console.log(temperature); console.log(dweet.thing); //
              "my-thing"
              console.log(dweet.content); // The content
              of the dweet
              console.log(dweet.created); // The create
              date of the dweet
            });
          setTimeout(dweetSend,10000);
        }
      dweetSend();

为了解释代码,让我们将其分解成几个部分。如您所见,我们有一个名为dweetSend的主函数,它在每 10 秒后重复一次。最初,我们需要从温度传感器获取数据。我们已经将温度传感器连接到模拟引脚 3。如果您仔细查看在 Arduino IDE 中编写的代码,您会发现很多相似之处:

vardweetClient = require("node-dweetio");
vardweetio = new dweetClient();

在这些行中,我们导入 node 的dweetio库。下一部分代码类似于 Arduino IDE,其中我们读取模拟读取的原始值并执行所需的计算:

var mraa= require('mraa');
var B=4275; varR0=100000;
var tempPin=new mraa.Aio(0);
var a=tempPin.read();
var R=1023/a-1;
R=100000*R;
var temperature=1/(Math.log(R/100000)/B+1/298.15)-273.15; temperature = +temperature.toFixed(2);

如前所述的代码中所述,我们还将温度值四舍五入到两位小数。接下来,我们将值推送到dweet.io的 thing 通道。在这里,我们需要提到我们的 thing 名称WeatherStation,然后是参数名称Temperature和变量名称temperature

dweetio.dweet_for("WeatherStation", {Temperature:temperature}, function(err, dweet)
  {
    console.log(temperature); console.log(dweet.thing); // "my-thing"
    console.log(dweet.content); // The content of the dweet
    console.log(dweet.created); // The create date of the dweet
  });

整个工作流程就是这样。所以,总结一下:

  1. 导入所需的库。

  2. 根据电路,从使用的引脚读取原始值,并处理该值以获得所需的输出。

  3. 将值推送到dweet.io

将此代码保存为dweetEdison.js,要运行此代码,请输入以下命令:

node dweetEdison.js

通过在 PuTTY 终端中执行前面的语句来运行代码后,您将看到以下输出:

PuTTY console—output

有时,在导入mraa库时,前面的代码可能会抛出错误。存在读取模拟引脚值的问题。这通常发生在您从多个来源安装了mraa库的情况下。在这种情况下,重新刷新您的设备并遵循所有步骤。

现在,一旦您看到这个,那么您的代码就运行成功了。现在我们将转到dweet.io页面,看看我们的数据是否真的被上传了。我们命名为 WeatherStation。您的 thing 名称显然会有所不同,因为它是唯一的。

现在浏览到以下 URL:

https://dweet.io/follow/YOUR_THING_NAME

您应该有一个类似这样的图表或视觉表示:

温度曲线图—dweet.io

除非我们有仪表,否则图表看起来不完整。有一个叫做freeboard.io的东西。它也是免费的,但同样它是公开的。所以,浏览到freeboard.io并登录或如果你没有账户则注册:

图片

freeboard.io—账户页面

提供一个名称并点击“创建新”。这里我们提供了 WeatherStation。一旦创建板,你将自动重定向到板设计页面。接下来,在 DATASOURCES 下,点击“添加”:

图片

Freeboard 截图

现在,一旦你点击“添加”,你必须选择一个类型。选择 Dweet.io。在名称下,提供任何名称。为了简单起见,使名称和设备名称相同。由于你的设备在dweet.io上是公开的,我们不需要提供密钥。点击“保存”继续:

图片

Dweet.io 数据源

完成后,我们需要为我们的传入数据添加一个面板。为此,点击“添加面板”并继续添加一个新的仪表。填写以下截图所示的详细信息。注意,在值字段下,我们已写入datasources["WeatherStation"]["Temperature"]。因此,WeatherStation是您的设备名称,后面跟着我们想要显示的参数名称。同样,本章即将讨论的项目也将有其他参数:

图片

Freeboard

点击“保存”,你应该能在你的板主页上看到你的仪表。

由于你的代码尚未运行,这个仪表目前不会显示任何值。所以回到你的 PuTTY 控制台并运行你的 Node.js 代码。一旦你的代码启动并运行,那么仪表应该代表你的传入数据,如以下截图所示:

图片

工作模式下的仪表

因此,我们已经部署了一个非常简单的用例,从 Edison 上传数据到dweet.io并在freeboard.io上显示。在下一节中,我们将处理一个实时物联网项目,我们将处理多个参数。

物联网项目实时用例 - 迷你气象站

在上一节中,我们看到了仅上传温度数据的用例。现在,使用迷你气象站,我们将处理三个参数:

  • 温度

  • 污染等级

  • 声音等级

所有三个参数都将上传到云端并在仪表板上实时显示。项目将被分成几个部分,这将使其更容易理解,我们还将处理其他复杂项目:

  • 系统架构

  • 传感器和硬件组件以及详细的电路图

  • 在控制台上本地获取数据并显示

  • 将数据上传到云端

  • 可视化数据

大多数细节之前已经使用温度传感器展示过了,所以我们将不会深入探讨,但会涵盖所有方面。

系统架构

系统的架构将与本章开头讨论的通用系统架构非常相似,只是缺少执行器和警报组件:

图片

气象站的架构

如前图所示,我们有三个传感器(温度、烟雾和噪音)向控制器(即英特尔爱迪生)提供原始数据,控制器处理原始数据并将其上传到dweet.io的物联网平台,最终在freeboard.io的仪表板上显示。对于这个项目,我们不对警报和执行器感兴趣,所以它们不包括在内。

硬件组件和详细电路图

为了实现项目,我们将处理以下硬件组件:

  • Grove 温度传感器

  • Grove 声音传感器

  • MQ2 烟雾传感器

前面的三个传感器在引脚配置上或多或少有相似之处,所以连接相当简单。正如之前解释的,我们将使用英特尔爱迪生的模拟引脚来接收传感器的输入。

MQ2 传感器返回原始值。在这里,我们将使用原始值并根据烟雾水平校准传感器。

以下是系统的电路图。请注意,我们只提到了将要使用的引脚:

图片

电路图 - 气象站

我们已经制作了一个公共VccGnd连接,所有传感器都连接在这里。接下来,我们需要三个模拟引脚。这些引脚连接到传感器的模拟输出引脚。需要注意的是,一些传感器可能有两个输出引脚,其中一个可能是模拟输出,而另一个是数字输出。由于我们只对模拟输出感兴趣,所以不会使用数字输出引脚。在这个项目中,MQ2 传感器(烟雾传感器)有这样的配置。

对于校准烟雾传感器,我们需要一些传感器敏感的烟雾存在。虽然我们将推送原始值,但对于本地警报,可以使用阈值技术。

根据前面的电路图连接您的传感器和爱迪生设备。一旦连接,您就可以使用代码了。

气象站阶段 1 的代码,从所有传感器获取数据并在控制台显示

在继续代码之前,让我们先看看算法:

  1. 导入库。

  2. 初始化输入引脚。

  3. 读取原始值。

  4. 处理值。

  5. 在控制台显示它。

这与前面的例子类似,唯一的区别是我们将使用三个传感器。在这种情况下,代码将用 Node.js 编写,因为稍后我们将将其推送到云端,即dweet.io

functiondisplayRes() 

{ 

//Import mraa 

var mraa= require('mraa'); var B=4275; 
var R0=100000; 

//Temperature pin 

var tempPin=new mraa.Aio(0); 

//Sound pin 

varsoundPin= new mraa.Aio(1); 

//Smoke pin 

varpolPin= new mraa.Aio(2); 

//Processing of temperature var a=tempPin.read(); 
var R=1023/a-1; R=100000*R; 
var temperature=1/(Math.log(R/100000)/B+1/298.15)-273.15; temperature = +temperature.toFixed(2); 
//Smoke read 

varsmokeValue= polPin.read(); 

//Sound read 

varsndRead= soundPin.read(); 

console.log("Temperature=  ",temperature);console.log("Soundlevel=   ",sndRead);console.log("Smoke level= ", smokeValue); setTimeout(displayRes,500); 
} 

displayRes(); 

在解释代码之前,我们只对温度传感器进行了处理。对于声音水平,我们将发送原始值,因为要将值转换为分贝,这是一个相对量我们需要访问两个实例的声音压力。因此,我们将限制自己使用原始值。然而,我们当然可以找到原始读数的阈值,并使用该阈值来触发一个动作,例如打开 LED 灯或发出蜂鸣声。

现在,让我们仔细看看代码。大部分代码与温度模块的代码相似。我们为烟雾和声音检测添加了一些额外的行:

//Sound pin
varsoundPin= new mraa.Aio(1);
//Smoke pin
varpolPin= new mraa.Aio(2);

在前面的行中,我们声明了用于声音传感器输入和烟雾传感器输入的模拟引脚。在接下来的行中,我们将读取值:

//Smoke read
varsmokeValue= polPin.read();
//Sound read
varsndRead= soundPin.read();

最终,我们使用控制台显示捕获的值。

当处理烟雾和声音等传感器时,我们可能无法直接获得标准单位值。在这些情况下,我们需要手动校准传感器来模拟已知值的 环境。假设在烟雾传感器的情况下,我们设置了一个我们知道值的环境,然后通过调节电位器改变可变电阻的值,从而校准传感器。这是许多标准程序之一。校准将在我们介绍机器人模块时详细讨论。

当你在控制台运行前面的代码时,你将获得所有传感器的输出。尝试在烟雾传感器周围增加烟雾或在大声传感器前大声说话以增加值,或者将温度传感器靠近笔记本电脑的通风口以获得更高的读数。以下是传感器获得值的截图:

传感器读数输出

一旦获得读数,我们就可以将它们推送到云端并在仪表板上显示。

这里,如果你注意到你没有获得正确的读数,那么你需要调整传感器上的电位器来手动校准它。为了将其上传到云端,我们需要在代码中做一些更改。请参考以下代码,以将所有三个数据推送到dweet.io

function dweetSend()
  {
    vardweetClient = require("node-dweetio"); vardweetio = new
    dweetClient();
//Import mraa
    var mraa= require('mraa'); var B=4275;
    var R0=100000;
//Temperature pin
    var tempPin=new mraa.Aio(0);
//Sound pin
    varsoundPin= new mraa.Aio(1);
//Smoke pin
    varpolPin= new mraa.Aio(2);
//Processing of temperature var a=tempPin.read();
    var R=1023/a-1; R=100000*R;
    var temperature=1/(Math.log(R/100000)/B+1/298.15)-273.15;
    temperature = +temperature.toFixed(2);
//Smoke read
    varsmokeValue= polPin.read();
//Sound read
    varsndRead= soundPin.read();
    dweetio.dweet_for("WeatherStation",
      {Temperature:temperature, SmokeLevel:smokeValue,
      SoundLevel:sndRead}, function(err, dweet)
        {
          console.log(dweet.thing); // "my-thing"
          console.log(dweet.content); // The content
          of the dweet
          console.log(dweet.created); // The create
          date of the dweet
        });
      setTimeout(dweetSend,10000);
    }
dweetSend();

在前面的代码中,你又会发现与温度代码有很多相似之处。在这里,我们执行了三次读取操作,并分别发送了代表该参数的所有三个值。以下行中可以明显看出:

dweetio.dweet_for("WeatherStation", {Temperature:temperature, SmokeLevel:smokeValue, SoundLevel:sndRead}, function(err, dweet)

通过遵循之前讨论的类似过程,使用 FileZilla 传输代码,并使用node命令执行它:

PuTTY 终端

看一下前面的截图,很明显数据正在被发送。现在,请注意声音和烟雾的值。最初,正在播放音乐,所以我们得到了 20-70 的范围内的声音值。对于烟雾传感器,标准值大约在 250-300 之间。在上一次读取中,我添加了一些烟雾,它飙升至 374。现在浏览到你的 dweet.io 门户,你会注意到数据正在实时更新:

Dweet.io 实时数据截图

一旦我们在这一侧设置好东西,我们将在 freeboard.io 上添加两个额外的仪表进行可视化。登录到 freeboard.io 并按照之前解释的方法添加仪表。在匹配需要指定参数的 DATASOURCES 时要具体:

Freeboard.io 最终布局

完成这些后,嗯,你将拥有自己的气象站,并且可以投入使用。一旦我们理解了概念,实现这些小型项目就极其容易。

读者开放式任务

由于你已经对如何实现小型物联网项目有了相当的了解,我们将为你提供一个简单的开放式任务,供你实现。

我们使用了 dweet.iofreeboard.io。除此之外,还有其他几个物联网平台,如 Azure、IBM Bluemix 和 Thingspeak。你的任务是模拟其他物联网平台上的类似功能。Azure 和 IBM 要求你使用借记卡或信用卡注册,但前一个月是免费的,并且可以用于物联网,而另一方面,Thingspeak 是免费的,并且支持 Matlab。

摘要

在本章中,我们学习了物联网系统的架构,如何处理问题陈述,我们还学习了传感器及其接口。然后我们转向物联网平台,并可视化了一些温度数据。最后,我们构建了一个小型气象站,它可以测量温度、声音水平和污染水平。我们还简要讨论了传感器的校准。

在第三章,“英特尔爱迪生和物联网(家庭自动化)”,我们将处理平台到控制器的通信,即通过互联网控制设备。然后我们将参与一个家庭自动化项目,我们将使用 Android 和 WPF 应用程序控制两个设备。

第三章:英特尔爱迪生和物联网(家庭自动化)

在第二章,气象站(物联网),我们处理了从爱迪生到云平台的数据传输。在这里,在本章中,我们将做相反的事情。我们将通过互联网控制设备。当我们谈论物联网时,通常首先想到的是家庭自动化。家庭自动化基本上是通过一个接口来控制和监控家用电器,这个接口可能是一个移动应用程序、一个网页界面、一个墙面触摸单元,或者更简单地说,是你的声音。因此,在本章中,我们将处理使用 MQTT 协议的家庭自动化的各种概念;然后,我们将使用 Android 应用程序和Windows 演示基础WPF)应用程序通过 MQTT 协议控制一个电负载。我们将讨论的一些主题包括:

  • 使用互联网 MQTT 协议控制设备的各种概念

  • 使用爱迪生通过 MQTT 协议推送数据和获取数据

  • 使用 MQTT 协议控制 LED

  • 使用 MQTT 协议的家庭自动化用例

  • 控制器应用在 Android(MyMqtt)和 WPF(待开发)

本章将使用一个名为 MyMqtt 的配套应用程序,该应用程序可以从 Play 商店下载。感谢开发者(Instant Solutions)开发此应用程序并将其免费上传到 Play 商店。MyMqtt 可以在这里找到:https😕/play.google.com/store/apps/details?id=at.tripwire.mqtt.client&hl=en

我们将开发自己的控制器,作为 WPF 应用程序,以实现协议并控制你的 Edison。

要开发 WPF 应用程序,我们将使用 Microsoft Visual Studio。您可以在这里下载它。

通过互联网控制设备 - 概念

当涉及到通过互联网控制设备时,一些关键因素开始发挥作用。首先,是使用的技术。这个领域有很多技术。一种快速的解决方案是使用 REST 服务,例如 HTTP GET 请求,我们从现有的数据库中获取数据。

这里讨论了一些解决方案。

REST 服务

获取所需数据最常用的技术之一是通过 HTTP GET 调用。市场上大多数物联网平台都公开了 REST API。在那里,我们可以通过 HTTP POST 请求从设备向平台发送值,同时通过 HTTP GET 请求获取数据。实际上,在第二章“气象站(物联网)”,我们使用 dweet.io 从设备发送数据时,我们使用了 SDK。内部,SDK 也执行类似的 HTTP POST 调用来发送数据。

指令或警报(存在于大多数物联网平台上)

在某些物联网平台中,我们有一些现成的解决方案,我们只需要调用某个网络服务,连接就会建立。内部,它可能使用 REST API,但为了用户的便利,他们已经推出了自己的 SDK,我们在其中实现。

内部,一个平台可能遵循 REST 调用、MQTT 或 Web Sockets。然而,我们只是使用 SDK,我们不直接实现它,通过使用平台的 SDK,我们能够建立连接。这完全取决于平台。在这里,我们讨论了一种解决方案,我们使用 MQTT 协议直接控制我们的设备,而不使用任何物联网平台。

架构

在典型的系统中,物联网平台充当用户和控制器协议之间的桥梁,如下面的图所示:

图片

控制设备的物联网系统架构

前面的图像展示了使用互联网控制设备的典型工作流程或架构。需要注意的是,用户可以直接控制控制器,而无需使用物联网平台,就像我们在这里做的那样。然而,通常用户会使用物联网平台,它还提供了更高级别的安全性。用户可以使用任何网页界面、移动应用程序或墙面控制单元,通过任何标准协议来控制设备。在此图像中,只包括了 REST、MQTT 和 Web Sockets。然而,还有更多可以使用的协议,例如 AMQP 协议、MODBUS 协议等。协议的选择主要取决于系统的敏感性和系统需要达到的稳定性。

MQTT 协议概述

MQTT 协议基于发布-订阅架构。它是一个非常轻量级的协议,其中消息交换是异步进行的。MQTT 协议的主要用途是在带宽和计算能力较低的地方。建立 MQTT 连接需要较小的代码占用空间。MQTT 协议中的每条通信都通过一个称为代理的中介进行。代理可以是订阅者或发布者。如果你想从爱迪生向服务器发送数据,那么你将通过代理发布数据。仪表板或应用程序使用通道凭证订阅代理,并提供数据。同样,当我们从任何应用程序控制设备时,爱迪生将作为订阅者,而我们的应用程序将作为发布者。这就是整个系统的工作方式。下面的屏幕截图解释了这一概念:

图片

在爱迪生作为发布者的溢出情况

在前面的屏幕截图中,我们看到爱迪生作为发布者。这是一种使用案例,我们需要从爱迪生发送数据,就像在第二章中展示的类似示例,气象站(物联网)。应用程序将获取数据并作为发布者。下面的屏幕截图展示了本章将使用的使用案例:爱迪生作为订阅者的使用:

图片

在爱迪生作为订阅者的溢出情况

在前面的案例中,我们对应用程序有一些控制。这些控制通过 MQTT 代理向爱迪生发送信号。现在,在这种情况下,应用程序将作为发布者,而爱迪生作为订阅者。

需要注意的是,在单个系统中,你可以使端点(设备或应用程序)同时作为发布者和订阅者。这种情况发生在我们想要从物联网设备(如英特尔爱迪生)获取数据,以及在紧急情况下控制设备时。同样,当我们需要控制家用电器的开关,以及远程监控它们时,也可能发生这种情况。尽管大多数系统是基于闭环反馈控制部署的,但总有远程监控的空间,同时根据从传感器接收到的反馈进行控制。

要实现 MQTT 协议,我们不会设置自己的服务器,而是使用现有的服务器。iot.eclipse.org/ 提供了一个沙盒服务器,将用于即将到来的项目。我们只需设置我们的代理,然后发布和订阅代理。对于 Intel Edison 方面,我们将使用 Node.js 及其相关库。对于应用程序端,我们将使用名为 MyMqtt 的现有 Android 应用程序。如果有人想开发自己的应用程序,则需要导入 paho 库来设置 MQTT。我们还在开发一个 PC 应用程序,其中我们将再次使用 MQTT 进行通信。

有关 Eclipse IoT 项目中 MQTT 和其他标准的详细信息,请参阅以下链接:

iot.eclipse.org/standards/

在下一节中,我们将为我们的项目设置和配置 Edison,并为 WPF 应用程序设置开发环境。

可以通过此链接访问 paho 项目:

eclipse.org/paho/

使用 Intel Edison 通过 MQTT 协议推送数据

如前所述,本节将向用户展示如何使用 MQTT 协议从 Edison 推送数据到 Android 设备。以下截图显示了工作流程:

从 Edison 推送数据到 Android 应用程序的流程

从前面的示意图中可以看出,我们首先从温度传感器获取读数,然后使用 MQTT 代理将读数推送到 Android 应用程序。

首先,我们将把温度传感器连接到 Edison。参考 第二章,气象站(物联网) 中的电路图。连接完成后,启动您的编辑器编写以下 Node.js 代码:

var mraa = require('mraa'); var mqtt = require('mqtt'); var B=4275;

var R0=100000;

var client = mqtt.connect('mqtt://iot.eclipse.org');
function sendData()

{

  var tempPin=new mraa.Aio(0);

//Processing of temperature var a=tempPin.read();

  var R=1023/a-1; R=100000*R;

  var temperature=1/(Math.log(R/100000)/B+1/298.15)-273.15; temperature
  = +temperature.toFixed(2);

//Converting type int to type string

  var sendTemp= temperature.toString();

//Publish the processed data client.publish('avirup/temperature',sendTemp); console.log("Sending data of temperature %d", temperature); setTimeout(sendData,1000);

}

sendData();

这里编写的代码与我们之前在 第二章,气象站(物联网) 中使用的代码类似。这里的区别是我们不是将其发送到 dweet.io,而是发送到 MQTT 代理。我们在 MQTT 代理的特定通道中发布获取到的数据。

然而,要执行此代码,您必须通过 npm 安装 MQTT 依赖项。在 PuTTY 控制台中输入以下命令:

npm install mqtt

这将安装 MQTT 依赖项。

在前面的代码中,我们最初导入了所需的库或依赖项。在这种情况下,我们需要 mraamqtt 库:

var mraa = require('mraa'); 
var mqtt = require('mqtt');

然后,我们需要初始化模拟引脚以读取温度。之后,我们将原始读数转换为标准值。

我们声明客户端变量,该变量将处理 MQTT 发布操作:

var client = mqtt.connect('mqtt://iot.eclipse.org');

这里,iot.eclipse.org/ 是我们使用的免费代理。

接下来,在 sendData 函数中,在将数据发布到通道之前,计算了初始的温度处理:

client.publish('avirup/temperature',sendTemp);

频道的名称是avirup/temperature。请注意sendTemp的类型。初始处理后的值存储在变量 temperature 中。在这里,在client.publish中,第二个参数必须是一个字符串。因此,我们将温度值存储为字符串类型在sendTemp中。最后,我们将温度打印到控制台。

我们还提供了一个 1 秒的延迟。现在使用node命令运行这个 Node.js 文件。

截图如下:

图片

输出控制台日志

如前一个截图所示,日志被显示出来。现在我们需要在 Android MyMqtt 应用程序中看到这些数据。

在执行这个迷你项目以及随后在 MQTT 下讨论的项目时,请更改频道名称。我的一个项目可能是实时运行的,可能会引起问题。可以采用NAME_OF_THE_USER/VARIABLE_NAME约定。

在 Android 中打开 MyMqtt 应用程序并浏览到设置。在那里,在代理 URL 字段中插入iot.eclipse.org。你已经在你的 Node.js 片段中使用过这个了:

图片

MyMqtt—1 的截图

接下来,转到“订阅”选项并输入基于你的 Node.js 代码的频道名称。在我们的例子中,它是avirup/temperature

图片

MyMqtt—2 的截图

点击添加以添加频道,然后最终转到仪表板以可视化你的数据:

图片

MyMqtt—3 的截图

如果你的设备上的代码与此并行运行,那么你应该在这个仪表板中获得实时数据流。

因此,现在你可以可视化从 Edison 发送的数据。

通过 MQTT 将数据发送到 Edison

我们一直在谈论家庭自动化控制电气负载,但每件事都有一个起点。最基本的启动器是控制通过互联网的 Edison——这就是它的全部内容。

当你有一个可以通过互联网控制的设备时,我们建议控制电气负载。在这个其他迷你项目中,我们将控制一个已经连接到 Intel Edison 的引脚13的简单 LED。为此不需要任何外部硬件,因为我们使用的是内置功能。现在,打开你的编辑器并输入以下代码:

var mraa = require('mraa'); var mqtt = require('mqtt');

varledPin=new mraa.Gpio(13); ledPin.dir(mraa.DIR_OUT);

var client = mqtt.connect('mqtt://iot.eclipse.org'); client.subscribe('avirup/control/#') client.handleMessage=function(packet,callback)

{

  var payload = packet.payload.toString() console.log(payload);
  if(payload=='ON')

  ledPin.write(1); if(payload=='OFF') ledPin.write(0);
  callback();

}

前面的代码将订阅代理中的频道并等待接收信号。

初始时,我们将 GPIO 引脚13声明为输出模式,因为板载 LED 连接到这个引脚:

图片

板载 LED 位置

前一个图像显示了板载 LED 的位置。

仔细查看代码,我们看到它最初导入库并设置 GPIO 引脚配置。然后,我们使用变量 client 初始化到代理的 MQTT 连接。

之后,我们继续将我们的设备订阅到频道,在这个例子中,该频道被命名为avirup/control/#

我们有一个事件处理器,handleMessage()。此事件处理器将处理传入的消息。传入的消息将存储在包变量中。我们还实现了一个回调方法,callback(),它需要在 handleMessage() 中调用。

这使我们能够接收多条消息。请注意,与 Node.js 的其他片段不同,我们没有实现任何循环。功能实际上是由 callback() 方法处理的。

最后,在函数内部,我们获取有效载荷,即消息。然后将其转换为字符串,并执行条件检查。我们还打印接收到的值到控制台。

现在将此代码通过 FileZilla 推送到您的 Edison 并运行代码。

一旦运行代码,您在控制台将看不到任何内容。原因是没有任何消息。现在,转到 Android 应用程序 MyMqtt,浏览到应用程序的发布部分。

我们需要在这里插入通道名称。在这种情况下,它是 avirup/control

图片

发布 MyMqtt

在主题部分,输入通道名称,在消息部分输入要发送给 Edison 的消息。

现在,同时运行您的 Node.js 代码。

一旦您的代码运行起来,我们将发送一条消息。在消息字段中键入 ON 并点击发布:

图片

发送控制信号

一旦您从应用程序中发布,它应该会在 PuTTY 控制台中反映出来:

图片

消息发送和接收——MQTT

现在您应该看到 LED 已经点亮。

同样,发送一条消息 OFF 来关闭板载 LED:

图片

消息发送和接收。LED 应该熄灭

值得注意的是,即使 Edison 和设备没有连接到同一网络,这也会正常工作。

现在您可以使用您的 Android 应用程序控制您的 Intel Edison。从虚拟的角度来看,您现在可以控制您的家了。在下一节中,我们将深入了解家庭自动化场景,并开发一个用于控制的 WPF 应用程序。

使用 Intel Edison、MQTT、Android 和 WPF 进行家庭自动化

到目前为止,我们已经了解了 MQTT 协议以及如何使用应用程序和 Edison 订阅和发布数据。现在我们将处理一个实际用例,我们将使用 Intel Edison 控制一个电气负载,它再次将通过互联网控制。以下是关于我们将要处理的内容的简要介绍:

  • 硬件组件和电路

  • 开发用于控制 Intel Edison 的 WPF 应用程序

  • 使用 MQTT 将一切连接起来

由于我们已经看到了如何使用 Android 应用程序控制 Edison,本节不会专注于这一点;相反,它将主要处理 WPF 应用程序。这只是为了给您一个简要的了解,了解一台 PC 如何控制物联网设备,不仅限于家庭自动化,还包括各种其他用例,从简单的概念验证场景到行业标准解决方案。

硬件组件和电路

当我们处理电气负载时,我们绝对不能直接将其连接到爱迪生或其他任何板子上,因为这会导致烧毁。为了处理这些负载,我们使用一个称为继电器的接口电路。继电器在其原始形式上是一系列机电开关。它们在直流电压下工作,并控制交流电源。以下列出了将要使用的组件:

  • 英特尔爱迪生

  • 5V 继电器模块

  • 电灯泡电线

在进入电路之前,我们首先讨论继电器:

继电器电路图。图片来源:http😕/www.phidgets.com/docs/3051_User_Guide

红色矩形区域代表电磁铁。我们用直流电压激发电磁铁,这会触发机械开关。仔细观察前面的图像,我们可以看到三个连接交流负载的端口:公共端口、常闭端口和常开端口。在默认条件下,即当电磁铁未被激发时,公共端口和常闭端口是连接的。目前我们感兴趣的是常开端口。

使用的继电器图像如下所示:

继电器单元。图片来源:Seed Studio

电气负载将有一个火线和零线。根据以下电路连接其中之一:

基本继电器连接

参考前面的图示,VccGnd 连接到控制器。交流电源直接连接到电负载的一端,而另一端通过继电器连接。其中一部分连接到公共端口,而另一部分可能是常闭NC)或常开NO)。当你将电负载的另一端连接到 NC 端口时,那么默认情况下,在没有电磁铁激励的情况下,电路是完整的。由于我们不希望当电磁铁未激励时灯泡在运行,所以将其连接到NO端口,而不是NC。因此,当通过在VccGnd上施加电压来激励电磁铁时,机械开关翻转到NO位置,从而将其与公共端口连接。

交流继电器操作背后的整个想法是使用机电开关来完成电路。然而,值得注意的是,并非所有继电器都基于相同的原理运行;一些继电器使用固态器件来操作。

固态继电器SSR)与机电继电器不同,没有可移动部件。SSR 使用光电耦合器来隔离输入和输出。它们将电信号转换为光信号,这些信号通过空间传播,从而隔离整个电路。接收端的光耦合器连接到任何开关设备,例如 MOSFET,以执行开关动作。

使用 SSR 而不是机电继电器有一些优点。如下所示:

  • 它们提供高速、高频的开关操作

  • 接触点有故障

  • 它们产生的噪音最小

  • 它们不会产生操作噪音

尽管我们现在将使用机电继电器,但如果用例涉及高频开关,则最好选择 SSR。还应注意,当长时间使用时,SSR 会变热。

最终电路

整个连接在以下图中显示:

家庭自动化项目的电路图

电路添加 Intel Edison 是因为继电器电路将由控制器控制。这里的继电器仅作为 AC 负载的接口单元。

当继电器正在运行时,请不要触摸其底部,否则可能会遭受 AC 电击,这可能是危险的。

要测试电路是否工作,请尝试使用 Arduino IDE 编写一个简单的程序:

#define RELAY_PIN 13 void setup()
{
  pinMode(RELAY_PIN,OUTPUT); //Set relay pin to output
}
void loop
{
  digitalWrite(RELAY_PIN, HIGH); //Set relay to on position
}

代码应将开关的位置从 NC 位置切换到 NO 位置,从而完成电路,使你的灯泡发光。别忘了打开 AC 电源。

一旦你准备好了最终电路,我们将继续进行 WPF 应用程序的开发,该应用程序将控制 Edison。

使用 MQTT 控制 Intel Edison 的 Android 应用程序

在上一节中,我们看到了如何使用代理来订阅和发布到通道的 Android 应用程序。在本节中,我们将开发自己的 Android 应用程序,用于通过 MQTT 控制设备。本节不会集中讨论 Android 的设置,而是将集中在开发方面。我们将使用 Android Studio IDE 来开发应用程序。请确保它已配置了所有最新的 SDK。

打开您的 Android Studio:

Android Studio—1

现在,选择“开始一个新的 Android Studio 项目”:

Android Studio—设置应用程序名称

为您的应用程序输入一个名称;在这里,我们输入了MQTT。点击“下一步”继续:

Android Studio:设置 API 级别

现在请选择最小 SDK 版本。选择 API 23:Android 6.0(棉花糖)。现在让我们选择活动类型:

设置活动

选择“空活动”并点击“下一步”:

设置启动活动名称

给您的活动起一个名字并点击“完成”。设置项目可能需要几分钟时间。完成后,您可能会看到如下屏幕:

设计页面。activity_name.xml

如果您仔细查看项目文件夹,您会注意到我们有一些文件夹,如javaresvalues等。让我们更仔细地看看这些文件夹实际上包含什么:

  • java: 这包含项目中所有的.java源文件。主活动,命名为MainActivity.java,也包含在这个项目中。

  • res/drawable: 这是一个用于此项目可绘制组件的目录。目前它不会被使用。

  • res/layout: 这包含所有负责应用程序 UI 的文件。

  • res/values: 这是一个包含资源定义的xml文件的其他类型的目录,例如字符串和颜色。

  • AndroidManifest.xaml: 这是一个定义应用程序以及应用程序所需权限的清单文件。

  • build.gradle: 这是一个自动生成的文件,其中包含诸如compileSdkVersionbuildToolsVersionapplicationID等信息。

在这个应用程序中,我们将使用一个名为 eclipse paho库的第三方资源或库来处理 MQTT。这些依赖项需要添加到build.gradle

应该有两个build.gradle文件。我们需要在build.gradle(Module:app)文件中添加依赖项:

repositories 
{ 
  maven 
    {
      url "https://repo.eclipse.org/content/repositories/paho-
      snapshots/"
    }
}
dependencies 
{
  compile('org.eclipse.paho:org.eclipse.paho.android.service:1.0.3-
  SNAPSHOT')
    {
      exclude module: 'support-v4'
    }
}

应该已经存在一个依赖块,因此您不需要再次编写整个内容。在这种情况下,只需在现有的依赖块中写入compile('org.eclipse.paho:org.eclipse.paho.android.service:1.0.3-SNAPSHOT') { exclude module: 'support-v4'即可。粘贴代码后,Android Studio 将要求您同步 gradle。在继续之前同步 gradle 是必要的:

添加依赖项

现在我们需要向我们的项目中添加权限和服务。浏览到AndroidManifest.xml并添加以下权限和服务:

<service android:name="org.eclipse.paho.android.service.MqttService" >
</service>
<uses- permission android:name="android.permission.INTERNET" />

完成此操作后,我们将继续进行 UI 设计。UI 需要在layout下的activity_main.xml文件中进行设计。

我们将拥有以下 UI 组件:

  • EditText:这是用于代理

  • URL EditText:这是用于通道的EditText端口

连接按钮:

  • 按钮用于发送信号

  • 关闭按钮用于发送关闭信号

将之前提到的组件拖放到设计器窗口中。或者,你可以在文本视图中直接编写它。

以下是你参考的最终设计的 XML 代码。请在相对布局选项卡中编写你的代码:

<EditText 
  android:layout_width="wrap_content"
  android:layout_height="wrap_content" 
  android:text="android/edison" 
  android:id="@+id/channelID" 
  android:hint="Enter channel ID" 
  android:layout_centerVertical="true"
  android:layout_alignParentStart="true"
  android:layout_alignEnd="@+id/portNum" /> 

<Button 
  android:layout_width="wrap_content"
  android:layout_height="wrap_content" 
  android:text="On" 
  android:id="@+id/on"
  android:layout_below="@+id/connectMQTT"
  android:layout_alignParentStart="true" 
  android:layout_marginTop="45dp" /> 
<Button 
  android:layout_width="wrap_content"
  android:layout_height="wrap_content" 
  android:text="Off" 
  android:id="@+id/off"
  android:layout_alignTop="@+id/on" 
  android:layout_alignParentEnd="true" /> 

<EditText 
  android:layout_width="wrap_content"
  android:layout_height="wrap_content" 
  android:id="@+id/brokerAdd" 
  android:layout_alignParentTop="true"
  android:layout_alignParentStart="true" 
  android:layout_marginTop="40dp" 
  android:hint="Broker Address" 
  android:layout_alignParentEnd="true" 
  android:text="iot.eclipse.org" /> 

<EditText 
  android:layout_width="wrap_content"
  android:layout_height="wrap_content" 
  android:id="@+id/portNum" 
  android:layout_below="@+id/brokerAdd"
  android:layout_alignParentStart="true" 
  android:layout_marginTop="40dp" 
  android:hint="Port Default: 1883"
  android:layout_alignEnd="@+id/brokerAdd" 
  android:text="1883" /> 

<Button 
  android:layout_width="wrap_content"
  android:layout_height="wrap_content" 
  android:text="@string/connect" 
  android:id="@+id/connectMQQT" 
  android:layout_below="@+id/channelID"
  android:layout_alignParentStart="true"
  android:layout_alignEnd="@+id/channelID" />

现在单击设计视图;你会看到已经创建了一个 UI,它应该与以下截图中的类似:

应用程序设计

现在仔细查看前面的代码,尝试找出所使用的属性。基本的属性如heightwidthposition已设置,这从代码中可以理解。主要的属性是EditTexttextidhint。Android UI 中的每个组件都应该有一个唯一的 ID。除此之外,我们还设置了一个提示,以便用户知道在文本区域中应该输入什么。为了方便,我们定义了文本,这样在部署时就不需要再次进行设置。在最终的应用程序中,移除文本属性。还有一个选项可以从strings.xml获取值,该文件位于文本或提示的values下:

android:text="@string/connect"

现在我们已经准备好了 UI,我们需要实现使用这些 UI 组件通过 MQTT 协议与设备交互的代码。我们也有适当的依赖项。主要的 Java 代码写在MainActivity.java中。

在进一步进行MainActivity.java活动之前,让我们创建一个将处理 MQTT 连接的类。这将使代码更容易理解且更高效。查看以下截图以了解MainActivity.java文件的位置:

右键单击突出显示的文件夹,然后单击“新建 | Java 类”。这个类将处理应用程序和 MQTT 代理之间发生的所有必要的数据交换:

package com.example.avirup.mqtt;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.MqttSecurityException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.io.UnsupportedEncodingException;
/** * Created by Avirup on 16-02-2017\. */
public class MqttClassimplements MqttCallback 
{ 
  String serverURI, port, clientID; 
  MqttClientclient; 
  MqttCallback callback;
//ConstructorMqttClass(String uri, String port, String clientID) 
    {
      this.serverURI=uri;
      this.port=port;
      this.clientID=clientID; 
    }
  public void MqttConnect() 
    {
      try 
        { 
          MemoryPersistencepersistance = new MemoryPersistence();
          StringBuilderServerURI = new StringBuilder();
          ServerURI.append("tcp://"); 
          ServerURI.append(serverURI);
          ServerURI.append(":"); 
          ServerURI.append(port); 
          String finalServerUri = ServerURI.toString();
          client = new MqttClient(finalServerUri, clientID,
          persistance);
          client.setCallback(callback);
          client.connect(); 
        }
      catch (MqttSecurityException e) 
        { 
          e.printStackTrace(); 
        } 
      catch (MqttException e) 
        { 
          e.printStackTrace(); 
        }
      }
    public void MqttPublish(String message) 
      { 
        String commId=clientID;
        try
          {
            byte[]
            payload=message.getBytes("UTF-8"); 
            MqttMessagefinalMsg= new MqttMessage(payload);
            client.publish(clientID,finalMsg); 
          }
        catch (UnsupportedEncodingException e) 
          { 
            e.printStackTrace(); 
          } 
        catch (MqttPersistenceExceptione) 
          { 
            e.printStackTrace(); 
          } 
        catch (MqttException e) 
          { 
            e.printStackTrace(); 
          } 
        } 
      @Override
      public void connectionLost(Throwable cause) 
        {
     }
       @Override
       public void messageArrived(String topic, MqttMessage
       message) throws Exception 
         {
       }
      @Override
      public void deliveryComplete(IMqttDeliveryToken token) 
        {
      } 
    }

之前粘贴的代码乍一看可能很复杂,但一旦理解了它,实际上非常简单。假设读者对面向对象编程概念有基本的了解。

导入包的语句都是自动完成的。创建类后,实现MqttCallback接口。这将添加需要重写的抽象方法。

初始时,我们为这个类编写一个参数化构造函数。我们还创建了一个全局引用变量用于MqttClientMqttCallback类。还创建了三个全局变量用于serverURIportclientID

String serverURI, port, clientID; 
MqttClientclient; 
MqttCallback callback; 
MqttClass(String uri, String port, String clientID)
{
  this.serverURI=uri;
  this.port=port;
  this.clientID=clientID; 
}

参数是代理URIport号码和clientID

接下来,我们创建了三个全局变量,并将它们设置为参数。在MqttConnect方法中,我们最初形成一个字符串,因为我们只接受服务器 URI 作为输入。在这里,我们将其与tcp://和端口号连接,并创建一个MemoryPersistence类的对象:

MemoryPersistencepersistance = new MemoryPersistence(); StringBuilderServerURI = new StringBuilder(); ServerURI.append("tcp://"); 
ServerURI.append(serverURI); 
ServerURI.append(":"); 
ServerURI.append(port); 
String finalServerUri = ServerURI.toString();

接下来,我们使用new关键字为全局引用变量创建对象:

client = new MqttClient(finalServerUri, clientID, persistance);

请注意参数。

上述代码被 try-catch 块包围,以处理异常。catch 块如下所示:

catch(MqttSecurityException e) 
{ 
  e.printStackTrace(); 
} 
catch (MqttException e) 
{
  e.printStackTrace(); 
}

连接部分已完成。下一个阶段是创建将数据发布到代理的publish方法。

参数只是字符串类型的message

public void MqttPublish(String message) 
{ 
  String commId=clientID;
  try
    {
      byte[] payload=message.getBytes("UTF-8"); 
      MqttMessagefinalMsg= new MqttMessage(payload);
      client.publish(clientID,finalMsg); 
    }
  catch (UnsupportedEncodingException e) 
    { 
      e.printStackTrace(); 
    } 
  catch (MqttPersistenceException
    { 
      e.printStackTrace(); 
    } 
  catch (MqttException e) 
    { 
      e.printStackTrace(); 
    } 
}

使用client.publish来发布数据。参数是一个字符串,它是clientIDchannelID,以及一个MqttMessage类型的对象。MqttMessage包含我们的消息。然而,它不接受字符串。它使用一个字节数组。在 try 块中,我们首先将字符串转换为字节数组,然后使用MqttMessage类将最终消息发布到特定的频道。

对于这个特定的应用程序,不需要重写的方法,所以我们保持原样。

现在回到MainActivity.java类。我们将使用我们刚刚创建的MqttClass来进行发布操作。这里的任务是获取 UI 中的数据,并使用我们刚刚编写的类连接到代理。

默认情况下,MainActivity.java将包含以下代码:

packagecom.example.avirup.mqtt;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivityextends AppCompatActivity 
{ 
  @Overrideprotected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main); 
  } 
}

每次应用程序打开时,onCreate方法都会被触发。仔细观察活动生命周期后,这个概念将变得清晰。

生命周期回调包括:

  1. onCreate()

  2. onStart()

  3. onResume()

  4. onPause()

  5. onStop()

  6. onDestroy()

更多关于生命周期的细节可以从以下链接获取:

developer.android.com/guide/components/activities/activity-lifecycle.html

现在我们需要将一些引用变量分配给 UI 组件。我们将在全局级别上完成这个操作。

onCreate方法开始之前,即在override关键字之前,添加以下行:

EditTextserverURI,port,channelID; 
Button connect,on,off;

现在,在onCreate方法中,我们需要分配我们刚刚声明的引用变量,并将它们显式转换为类类型:

serverURI=(EditText)findViewById(R.id.brokerAdd);
port=(EditText)findViewById(R.id.port Num);
connect=(Button)findViewById(R.id.connectMQTT);
channelID=(EditText)findViewById(R.id.channelID);
on=(Button)findViewById(R.id.on);
off=( Button)findViewById(R.id.off);

在前面的行中,我们已将它们显式转换为EditTextButton,并将它们绑定到 UI 组件上。

现在我们将为连接按钮创建一个新的事件处理器:

connect.setOnClickListener(new View.OnClickListener() 
{ 
  @Overridepublic void onClick(View v) 
    {
    } 
});

当我们按下连接按钮时,前面的块会被激活。该块包含一个参数为 view 的方法。当按钮被按下时需要执行的代码需要写入 onCLick(View v) 方法中。

在此之前,为之前创建的类创建一个全局引用变量:

MqttClassmqttClass;

接下来,在方法内部,从编辑框中获取文本。事先声明那些类型为字符串的全局变量:

String serverUri, portNo,channelid;

现在,在 onClick 方法中编写以下代码:

serverUri=serverURI.getText().toString();
portNo=port.getText().toString();
channelid=channelID.getText().toString();

一旦我们获取了数据,我们将为 MqttClass 类创建一个对象,并将字符串作为参数传递,我们还将调用 MqttConnect 方法:

mqttClass=new MqttClass(serverUri,portNo,channelid); mqttClass.MqttConnect(); 

现在,我们将为 ONOFF 方法创建类似的案例:

on.setOnClickListener(new View.OnClickListener() 
{ 
  @Overridepublic void onClick(View v) 
    {
      mqttClass.MqttPublish("ON"); 
    } 
});
off.setOnClickListener(new View.OnClickListener() 
{
  @Overridepublic void onClick(View v) 
    {
      mqttClass.MqttPublish("OFF"); 
    } 
});

我们使用了 MqttClassMqttPublish 方法。参数只是一个字符串,基于当它被激活时发布数据的 onClick 方法。

现在应用程序已准备就绪,可以部署到您的设备上。您必须在您的 Android 设备上开启开发者模式,并将设备连接到 PC,然后按下运行按钮。现在您应该在设备上运行应用程序。要测试您的应用程序,您可以直接使用 Edison 或仅使用 MyMqtt 应用程序。

使用 MQTT 控制的 Windows Presentation Foundation 应用程序

WPF 是一个强大的 UI 框架,用于构建 Windows 桌面客户端应用程序。它支持广泛的应用程序功能,包括模型、控件、图形布局、数据绑定、文档和安全。编程基于 C# 的核心逻辑和 XAML 的 UI。

WPF 中的“Hello World”示例应用程序

在开始开发控制 Intel Edison 的应用程序之前,让我们简要看看我们如何集成某些基本功能,例如按钮点击事件、处理显示数据等。打开您的 Visual Studio 并选择新建项目。

在低内存的 PC 上,安装 Visual Studio 可能需要一段时间,首次打开 Visual Studio 也是如此:

我们使用 WPF 的原因是它将在多个主题中使用,例如本章以及即将到来的关于机器人的章节。在机器人领域,我们将开发用于控制机器人的软件。还假设读者对 Visual Studio 有一定的了解。有关如何使用 Visual Studio 和 WPF 的详细信息,请参阅以下链接:

msdn.microsoft.com/en-us/library/aa970268(v%3Dvs.110).aspx

在 WPF 中创建新项目

点击新建项目,然后在 Visual C# 部分,点击 WPF 应用程序。在名称字段中输入例如 Mqtt Controller 的名称,然后点击确定。

一旦您点击确定,项目将被创建:

WPF 项目已创建

创建项目后,您应该得到一个类似于下面的显示。如果窗口中缺少某些显示组件,请转到视图并选择它们。现在仔细查看解决方案浏览器,它在图像的右侧可见。

在那里,查看项目结构:

截图

解决方案浏览器

应用程序有两个主要组件。第一个是 UI,它将在MainWindow.xaml中设计,第二个是逻辑,它将在MainWindow.xaml.cs中实现。

UI 使用 XAML 设计,而逻辑用 C#实现。

首先,我们只有一个按钮控件:一个用户将输入文本的字段和一个显示输入文本的区域。在我们对事件处理有了足够的了解之后,我们可以继续实现 MQTT。

最初,我们将为MainPage.xaml.cs中的双击设计 UI。这个文件是我们添加 UI 的 XAML 组件的地方。代码是用 XAML 编写的,大部分工作可以通过拖放功能完成。从应用右侧的工具箱中查找以下项:

  • Button

  • TextBlock

  • TextBox

添加组件有两种方式。第一种是手动在页面的 XAML 视图中添加代码,第二种是从组件工具箱中拖放。以下是一些需要注意的事项。

设计师窗口可以根据您的意愿进行编辑。一个快速的解决方案是选择您想要编辑的组件,这可以在属性窗口中完成。

属性也可以使用 XAML 进行编辑:

截图

Visual Studio 布局

在前面的截图中,我们已更改了背景颜色并添加了组件。注意属性窗口中突出显示的背景颜色。

TextBox是用户输入文本的区域,而TextBlock是显示文本的区域。一旦您在设计视图中放置了组件并编辑了它们的属性,主要是组件的名称,我们将添加事件处理器。为了快速实现前面截图中的设计,请在grid标签内编写以下 XAML 代码:

<Button x:Name="click_me" Content="Click me" HorizontalAlignment="Left" Margin="151,137,0,0" VerticalAlignment="Top" Width="193"/>
<TextBlock x:Name="textBlock" Text="TextBlock" HorizontalAlignment="Left"
Margin="151,189,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="193" Foreground="White"/>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="151,101,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="193"/>

现在在设计师窗口中,双击按钮以创建一个用于点击事件的处理器。可用的事件可以在属性窗口中查看,如下面的截图所示:

截图

按钮的事件属性

双击后,您将自动重定向到MainWindow.xaml.cs,同时还有一个为该事件自动生成的函数。

您将得到一个类似于以下代码的方法:

privatevoidclick_me_Click(object sender, RoutedEventArgs e)
{
}

在这里,我们将实现逻辑。最初,我们将读取TextBox中写入的数据。如果它是空的,我们将显示一条消息,说明它不能为空。然后,我们将只将消息传递给TextBlock。以下代码执行相同的功能:

privatevoidclick_me_Click(object sender, RoutedEventArgs e)
{
  string res = textBox.Text; if(string.IsNullOrEmpty(res))
    {
      MessageBox.Show("No text entered. Please enter again");
    }
  else
    {
      textBlock.Text = res;
    }
}

前面的代码最初读取数据,然后检查它是否为 null 或空,然后将数据输出到TextBlock

图片

应用程序运行—1

F5运行您的应用程序,然后前面的屏幕应该出现。接下来,删除文本框中的文本,然后点击“点击我”按钮:

图片

空文本

现在,在文本框中输入任何文本,然后点击“点击我”按钮。您输入的文本应该随后在文本块中显示:

图片

WPF HelloWorld

现在我们已经知道了如何创建一个简单的 WPF 应用程序,我们将编辑应用程序本身以实现 MQTT 协议。要实现 MQTT 协议,我们必须使用一个库,该库将通过 NuGet 包管理器添加。

现在,浏览到“引用”并点击“管理 NuGet 包”,然后添加M2Mqtt外部库:

图片

NuGet 包管理器

一旦我们有了包,我们就可以在项目中使用它们。对于这个项目,我们将在MainWindow.xaml中使用以下 UI 组件:

  • 一个用于输入频道 ID 的文本框

  • 一个用于显示最新控制命令的文本块

  • 一个用于设置状态为开启的按钮

  • 一个设置状态为关闭的按钮

  • 一个用于连接的按钮

随意设计 UI:

图片

控制应用程序的 UI

在前面的屏幕截图中,您将看到设计已更新,并且还添加了一个按钮。以下是将前面设计代码粘贴的代码。文本框是我们将输入频道 ID 的区域,然后我们将使用按钮来打开和关闭 LED,以及使用连接按钮连接到服务。现在,像之前做的那样,我们将为前面提到的两个按钮创建点击事件的处理器。要添加点击事件,只需在每个按钮上双击即可:

<Button x:Name="on" Content="on" HorizontalAlignment="Left" Margin="151,180,0,0" VerticalAlignment="Top" Width="88" Click="on_Click"/>
  <TextBlock x:Name="statusBox" Text="status"
  HorizontalAlignment="Left" Margin="229,205,0,0" TextWrapping="Wrap"
  VerticalAlignment="Top" Width="115" Foreground="White"/>
  <TextBox x:Name="channelID" HorizontalAlignment="Left" Height="23"
  Margin="151,101,0,0" TextWrapping="Wrap" Text=""
  VerticalAlignment="Top" Width="193"/>
  <Button x:Name="off" Content="off" HorizontalAlignment="Left"
  Margin="256,180,0,0" VerticalAlignment="Top" Width="88"
  Click="off_Click"/>
  <Button x:Name="connect" Content="Connect" HorizontalAlignment="Left"
  VerticalAlignment="Top" Width="193" Margin="151,139,0,0"
  Click="connect_Click"/> 

前面的代码在网格标签中提到。

现在一旦有了设计,就转到MainWindow.xaml.cs并编写主要代码。您会注意到已经存在一个构造函数和两个事件处理器方法。

添加以下命名空间以使用库:

uPLibrary.Networking.M2Mqtt;

现在创建MqttClient类的实例并声明一个全局字符串变量:

MqttClient client = new MqttClient("iot.eclipse.org"); String channelID;

接下来,在连接按钮的事件处理器中,使用频道 ID 将其连接到代理。

连接按钮的事件处理器代码如下所示:

channelID_text = channelID.Text;
if (string.IsNullOrEmpty(channelID_text))
{
  MessageBox.Show("Channel ID cannot be null");
}
else
{
  try
  {
    client.Connect(channelID_text); connect.Content = "Connected";
  }
catch (Exception ex)
{
  MessageBox.Show("Some issues occured: " + ex.ToString());
}
}

在前面的代码片段中,我们从包含频道 ID 的textbox中读取数据。如果它是 null,我们会要求用户再次输入。然后,最后,我们将它连接到频道 ID。请注意,它位于try catch块内。

还有另外两个事件处理器。我们需要向它们连接的频道发布一些值。

在“开启”按钮的事件处理器中,插入以下代码:

private void on_Click(object sender, RoutedEventArgs e)
{
  byte[] array = Encoding.ASCII.GetBytes("on");
  client.Publish(channelID_text, array);
}

如前所述的代码所示,Publish方法的参数是主题,即channelID和一个包含消息的byte[]数组。

类似地,对于off方法,我们有:

private void off_Click(object sender, RoutedEventArgs e)
{
  byte[] array = Encoding.ASCII.GetBytes("off");
  client.Publish(channelID_text, array);
}

就这些了。这是您家庭自动化 MQTT 控制器的全部代码。以下代码已粘贴供您参考:

using System; 
usingSystem.Collections.Generic; 
usingSystem.Linq;
usingSystem.Text; 
usingSystem.Threading.Tasks; 
usingSystem.Windows; 
usingSystem.Windows.Controls; 
usingSystem.Windows.Data; 
usingSystem.Windows.Documents; 
usingSystem.Windows.Input; 
usingSystem.Windows.Media; 
usingSystem.Windows.Media.Imaging; 
usingSystem.Windows.Navigation; 
usingSystem.Windows.Shapes;
using uPLibrary.Networking.M2Mqtt; 
namespaceMqtt_Controller
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
  public partial class MainWindow : Window
    {
      MqttClient client = new MqttClient("iot.eclipse.org"); 
      String channelID_text;
      publicMainWindow()
        {
          InitializeComponent();
        }
      private void on_Click(object sender, RoutedEventArgs e)
        {
          byte[] array = Encoding.ASCII.GetBytes("ON");
          client.Publish(channelID_text, array); 
          statusBox.Text = "on";
        }
      private void off_Click(object sender, RoutedEventArgs e)
        {
          byte[] array = Encoding.ASCII.GetBytes("OFF");
          client.Publish(channelID_text, array); 
          statusBox.Text = "off";
        }
      private void connect_Click(object sender, RoutedEventArgs e)
        {
          channelID_text = channelID.Text;
          if (string.IsNullOrEmpty(channelID_text))
            {
              MessageBox.Show("Channel ID cannot be null");
            }
          else
            {
              try
                {
                  client.Connect(channelID_text); 
                  connect.Content = "Connected";
                }
              catch (Exception ex)
                {
                  MessageBox.Show("Some issues occured: " +
                  ex.ToString());
                }
            }
        }
    }
}

F5 或启动按钮来执行此代码:

图片

应用程序正在运行

接下来,在文本框中输入 channelID。在这里,我们将输入 avirup/control,然后我们将按下连接按钮:

图片

应用程序正在运行—2

现在打开您的 PuTTY 控制台并登录到英特尔爱迪生。使用 ifconfig 命令验证设备是否已连接到互联网。接下来,只需运行 Node.js 脚本。接下来,按下“开启”按钮:

图片

由 WPF 应用程序控制的 MQTT

类似地,按下“关闭”按钮时,您将看到以下类似的屏幕:

图片

由 WPF 控制的 MQTT

持续按下“开启”和“关闭”,您将在英特尔爱迪生上看到效果。现在,我们记得我们已经连接了继电器和电灯泡,现在应该可以看到效果。如果将交流电源的主开关关闭,那么您将看不到灯泡被打开,但您会听到“滴答”声。这表明继电器现在处于“开启”位置。硬件设置的图片如下所示:

图片

家庭自动化的硬件设置

因此,您已经准备好家庭自动化设置,您可以通过 PC 应用程序或 Android 应用程序来控制它。

如果您在办公室网络中,那么有时端口 1883 可能被阻止。在这种情况下,建议使用您自己的个人网络。

读者开放性任务

现在,您可能已经对家庭自动化中事物的工作方式有了大致的了解。我们在这一领域涵盖了多个方面。留给读者的任务不仅是集成单个控制命令,而是多个控制命令。这将使您能够控制多个设备。在 Android 和 WPF 应用程序中添加更多功能,并使用更多的字符串控制命令。将更多继电器单元连接到设备进行接口。

摘要

在本章中,我们了解了家庭自动化的基本概念。我们还学习了如何使用继电器控制电气负载。不仅如此,我们还学习了如何开发 WPF 应用程序并实现 MQTT 协议。在设备端,我们使用了 Node.js 代码将我们的设备连接到互联网,并通过代理订阅某些频道,最终接收信号来自动控制。在系统的 Android 端,我们使用了现成的 MyMqtt 应用程序,并使用它来获取和发布数据。然而,我们也详细介绍了 Android 应用程序的开发,并展示了如何使用它来实现 MQTT 协议来控制设备。

在第四章,英特尔爱迪生与安全系统,我们将学习如何使用英特尔爱迪生处理图像处理和语音处理应用。第四章,英特尔爱迪生与安全系统,将主要涉及 Python 编程以及一些开源库的使用。

第四章:英特尔爱迪生和安全系统

在前面的章节中,我们学习了如何使用英特尔爱迪生开发与物联网相关的应用程序,我们展示了实时传感器数据,并控制了爱迪生本身。我们还学习了开发 Android 和 WPF 应用程序,这些应用程序用于控制英特尔爱迪生。嗯,本章更多地关注英特尔爱迪生的本地前端,我们将使用设备的内置功能。本章主要集中在这两个关键点上:

  • 使用英特尔爱迪生进行语音和语音处理

  • 使用英特尔爱迪生进行图像处理

所有代码都应使用 Python 编写,因此本章的一些部分也将专注于 Python 编程。在本章中,我们将使用语音命令操作英特尔爱迪生,然后最终使用英特尔爱迪生和摄像头检测人脸。因此,本章将探讨英特尔爱迪生的核心功能。由于大部分代码都是 Python 编写的,建议您从以下网站下载 Python 到您的电脑:

www.python.org/downloads/

本章将分为两部分。第一部分将仅关注语音或语音处理,我们将基于此进行一个迷你项目;而第二部分将更详细,将关注使用 OpenCV 的图像处理方面。

使用爱迪生进行语音/语音处理

语音处理通常指的是应用于音频信号的各种数学技术,以对其进行处理。这可能是一些简单的数学运算,也可能是一些复杂的运算。它是数字信号处理的一个特例。然而,我们通常不将语音处理作为一个整体实体来处理。我们只对语音到文本转换的特定领域感兴趣。需要注意的是,本章中的一切都应由爱迪生本身执行,而不需要访问任何云服务。本章将首先解决的场景是我们将使爱迪生根据我们的语音命令执行一些任务。我们将使用轻量级的语音处理工具,但在继续所有代码和电路之前,请确保您有以下设备。最初,我们将向您展示如何开关 LED。接下来,我们将使用语音命令控制伺服电机。

所需设备

除了英特尔爱迪生外,我们还需要一些其他设备,如下所示:

  • 英特尔爱迪生的 9V-1 A 电源适配器

  • USB 声卡

  • USB 集线器,最好是供电的

本项目将使用爱迪生外部供电,USB 端口将用于声卡。非供电 USB 集线器也可以使用,但由于电流问题,建议使用供电 USB 集线器。

确保 USB 声卡在 Linux 环境中受支持。选择开关应朝向 USB 端口。这是因为 Edison 将通过直流适配器供电,我们需要在提供直流电源时才激活的 USB 端口供电。

语音处理库

对于这个项目,我们将使用 PocketSphinx。它是卡内基梅隆大学创建的 CMU Sphinx 的一个轻量级版本。它是一个轻量级的语音识别引擎,适用于移动、手持设备和可穿戴设备。使用这个比任何基于云的服务最大的优势是它可以离线使用。

更多关于 PocketSphinx 的信息可以从以下链接获取:

cmusphinx.sourceforge.net/wiki/develop

github.com/cmusphinx/pocketsphinx

设置库将在本章的后续部分讨论。

初始配置

在第一章中,我们对 Intel Edison 进行了一些非常基本的配置。在这里,我们需要使用所需的库和声卡设置来配置我们的设备。为此,您需要将 Intel Edison 连接到仅一个微型 USB 端口。这将用于通过 PuTTY 控制台进行通信,并使用 FileZilla FTP 客户端传输文件:

Arduino 扩展板组件

将 Intel Edison 连接到 Micro B USB,以通过串行接口连接到您的 PC。

一些步骤已在第一章中介绍,设置 Intel Edison;然而,我们将从开始展示所有步骤。打开您的 PuTTY 控制台并登录到您的设备。使用configure_edison -wifi连接到您的 Wi-Fi 网络。

最初,我们将添加 AlexT 的非官方opkg仓库。要添加它,编辑/etc/opkg/base-feeds.conf文件。

将以下行添加到前面的文件中:

src/gz all http://repo.opkg.net/edison/repo/all
src/gz edison http://repo.opkg.net/edison/repo/edison
src/gz core2-32 http://repo.opkg.net/edison/repo/core2-32  

要做到这一点,请执行以下命令:

echo "src/gz all http://repo.opkg.net/edison/repo/all
src/gz edison http://repo.opkg.net/edison/repo/edison
src/gz core2-32 http://repo.opkg.net/edison/repo/core2-32" >> /etc/opkg/base-feeds.conf 

更新包管理器:

opkg update  

使用包管理器安装git

opkg install git  

我们现在将安装 Edison 辅助脚本以简化一些事情:

  1. 首先克隆该包:
 git clone https://github.com/drejkim/edison-scripts.git ~/edison
      scripts

  1. 现在,我们必须将~/edison-scripts添加到路径中:
 echo'export PATH=$PATH:~/edison-scripts'>>~/.profile
 source~/.profile  

  1. 接下来我们将运行以下脚本:
 # Resize /boot -- we need the extra space to add an additional
      kernel resizeBoot.sh

 # Install pip, Python's package manager installPip.sh

 # Install MRAA, the low level skeleton library for IO
      communication on, Edison, and other platforms installMraa.sh

初始配置已完成。现在我们将为声音配置 Edison。

  1. 现在安装USB 设备的模块,包括 USB 摄像头、麦克风和扬声器。确保您的声卡已连接到 Intel Edison:
      opkg install kernel-modules

  1. 下一个目标是检查 USB 设备是否被检测到。要检查这一点,请输入lsusb命令:

USB 声卡

在前面的屏幕截图中显示了连接到 Intel Edison 的设备。它在框中被突出显示。一旦我们获取到连接到 Edison 的设备,我们就可以继续下一步。

现在我们将检查alsa是否能够检测到声卡。输入以下命令:

aplay -Ll  

Alsa 设备检查

注意,我们的设备被检测为卡 2,命名为 Device

现在我们必须创建一个 ~/.asoundrc 文件,在其中我们需要添加以下行。请注意,Device 必须替换为系统上检测到的设备名称:

pcm.!default sysdefault:Device

现在,一旦完成,退出并保存文件。接下来,为了测试一切是否正常工作,执行以下命令,你必须在连接的耳机上听到一些声音:

aplay /usr/share/sounds/alsa/Front_Center.wav

你应该听到“前中心”这个词。

现在,我们的目标是记录一些内容并解释结果。所以让我们测试一下记录是否正常工作。

要录制一段剪辑,输入以下命令:

arecord ~/test.wav

按 *Ctrl *+ c 停止录制。要播放前面的录音,输入以下命令:

aplay ~/test.wav

你必须听到你录制的声音。如果你听不到声音,输入 alsamixer 并调整播放和录音音量。最初,你需要选择设备:

Alsamixer—1

接下来,使用箭头键调整音量:

Alsamixer—2

现在所有与声音相关的设置都已经完成。下一个目标是安装语音识别的包。

初始时,使用 Python 的 pip 来安装 cython

pip install cython

前面的包安装需要很长时间。一旦安装完成,还有一些需要执行的 shell 脚本。我已经为这个创建了一个 GitHub 仓库,其中包含所需的文件和代码。使用 git 命令克隆仓库 (github.com/avirup171/Voice-Recognition-using-Intel-Edison.git):

 git clone 

接下来在 bin 文件夹中,你会找到这些包。在输入执行这些 shell 脚本的命令之前,我们需要提供权限。输入以下命令以添加权限:

chmod +x <FILE_NAME>

接下来输入要执行的文件名。安装包可能需要一些时间:

./installSphinxbase.sh

接下来输入以下内容以添加到路径:

echo 'export LD_LIBRARY_PATH=/usr/local/lib' >> ~/.profile
echo 'export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig' >> ~/.profile
source ~/.profile

接下来安装 Pocketsphinx

./installPocketsphinx.sh

最后,安装 PyAudio

./installPyAudio.sh

在这一步之后,所有配置都已经设置好,我们可以开始编码了。PocketSphinx 与一些特定的命令集一起工作。我们需要为要使用的单词创建一个语言模式和字典。我们将使用 Sphinx 知识库工具来完成这项工作:

www.speech.cs.cmu.edu/tools/lmtool-new.html

上传包含我们希望引擎解码的命令集的文本文件。然后点击编译知识库。下载包含所需文件的 .tgz 文件。一旦我们有了这些文件,使用 FileZilla 将其复制到 Edison 上。注意包含以下扩展名的文件名称。理想情况下,每个文件都应该有相同的名称:

  • .dic

  • .lm

将整个集合移动到 Edison 上。

编写代码

问题陈述:使用如 ONOFF 的语音命令来打开和关闭 LED。

在编写代码之前,让我们先讨论一下算法。请注意,我将算法以纯文本的形式编写,以便读者更容易理解。

让我们从算法开始

执行以下步骤以开始算法:

  1. 导入所有必要的包。

  2. 设置 LED 引脚。

  3. 启动一个无限循环。从现在开始,所有部分或块都将位于 while 循环内。

  4. 在路径中存储两个变量,用于.lm.dic文件。

  5. 记录并保存一个持续3秒的.wav文件。

  6. .wav文件作为参数传递给语音识别引擎。

  7. 获取结果文本。

  8. 使用if else块测试ONOFF文本,并使用mraa库来开关 LED。

算法相当直接。将以下代码与前面的算法进行比较,以全面掌握它:

import collections 
import mraa 
import os 
import sys 
import time 

# Import things for pocketsphinx 
import pyaudio 
import wave 
import pocketsphinx as ps 
import sphinxbase 

led = mraa.Gpio(13)   
led.dir(mraa.DIR_OUT) 

print("Starting") 
while 1: 
         #PocketSphinx parameters 
         LMD   = "/home/root/vcreg/5608.lm" 
         DICTD = "/home/root/vcreg/5608.dic" 
         CHUNK = 1024 
         FORMAT = pyaudio.paInt16 
         CHANNELS = 1 
         RATE = 16000 
         RECORD_SECONDS = 3 
         PATH = 'vcreg' 
         p = pyaudio.PyAudio() 
         speech_rec = ps.Decoder(lm=LMD, dict=DICTD) 
         #Record audio 
         stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE,
         input=True, frames_per_buffer=CHUNK) 
         print("* recording") 
         frames = [] 
         fori in range(0, int(RATE / CHUNK * RECORD_SECONDS)): 
               data = stream.read(CHUNK) 
               frames.append(data) 
         print("* done recording") 
         stream.stop_stream() 
         stream.close() 
         p.terminate() 
         # Write .wav file 
         fn = "test.wav" 
         #wf = wave.open(os.path.join(PATH, fn), 'wb') 
         wf = wave.open(fn, 'wb') 
         wf.setnchannels(CHANNELS) 
         wf.setsampwidth(p.get_sample_size(FORMAT)) 
         wf.setframerate(RATE) 
         wf.writeframes(b''.join(frames)) 
         wf.close() 

         # Decode speech 
         #wav_file = os.path.join(PATH, fn) 
         wav_file=fn 
         wav_file = file(wav_file,'rb') 
         wav_file.seek(44) 
         speech_rec.decode_raw(wav_file) 
         result = speech_rec.get_hyp() 
         recognised= result[0] 
         print("* LED section begins") 
         print(recognised) 
         if recognised == 'ON.': 
               led.write(1) 
         else: 
               led.write(0) 
         cm = 'espeak "'+recognised+'"' 
         os.system(cm) 

我们逐行来看:

import collections 
import mraa 
import os 
import sys 
import time 

# Import things for pocketsphinx 
import pyaudio 
import wave 
import pocketsphinx as ps 
import Sphinxbase 

前面的部分只是为了导入所有库和包:

led = mraa.Gpio(13)   
led.dir(mraa.DIR_OUT) 

我们设置了 LED 引脚并将其方向设置为输出。接下来,我们将开始无限 while 循环:

#PocketSphinx and Audio recording parameters 
         LMD   = "/home/root/vcreg/5608.lm" 
         DICTD = "/home/root/vcreg/5608.dic" 
         CHUNK = 1024 
         FORMAT = pyaudio.paInt16 
         CHANNELS = 1 
         RATE = 16000 
         RECORD_SECONDS = 3 
         PATH = 'vcreg' 
         p = pyaudio.PyAudio() 
         speech_rec = ps.Decoder(lm=LMD, dict=DICTD) 

前面的代码块只是 PocketSphinx 和音频记录的参数。我们将记录3秒。我们还提供了.lmd.dic文件的路径以及一些其他音频记录参数:

#Record audio 
         stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE,
         input=True, frames_per_buffer=CHUNK) 
         print("* recording") 
         frames = [] 
         fori in range(0, int(RATE / CHUNK * RECORD_SECONDS)): 
               data = stream.read(CHUNK) 
               frames.append(data) 
         print("* done recording") 
         stream.stop_stream() 
         stream.close() 
         p.terminate() 

在前面的代码中,我们记录了特定时间间隔的音频。

接下来,我们将它保存为.wav文件:

# Write .wav file 
         fn = "test.wav" 
         #wf = wave.open(os.path.join(PATH, fn), 'wb') 
         wf = wave.open(fn, 'wb') 
         wf.setnchannels(CHANNELS) 
         wf.setsampwidth(p.get_sample_size(FORMAT)) 
         wf.setframerate(RATE) 
         wf.writeframes(b''.join(frames)) 
         wf.close() 

最后一步包含文件的解码和比较以影响 LED:

# Decode speech 
         #wav_file = os.path.join(PATH, fn) 
         wav_file=fn 
         wav_file = file(wav_file,'rb') 
         wav_file.seek(44) 
         speech_rec.decode_raw(wav_file) 
         result = speech_rec.get_hyp() 
         recognised= result[0] 
         print("* LED section begins") 
         print(recognised) 
         if recognised == 'ON.': 
               led.write(1) 
         else: 
               led.write(0) 
         cm = 'espeak "'+recognised+'"' 
         os.system(cm) 

在前面的代码中,我们最初将.wav文件作为参数传递给语音处理引擎,然后使用结果进行比较。最后,根据语音处理引擎的输出开关 LED。前面代码执行的另一项活动是,使用espeak将识别的内容重新朗读出来。espeak是一个文本到语音引擎。它默认使用频谱共振峰合成,听起来像机器人,但可以配置为使用 Klatt 共振峰合成或 MBROLA 以产生更自然的音效。

使用 FileZilla 将代码传输到你的设备。假设代码被保存为名为VoiceRecognitionTest.py的文件。

在执行代码之前,你可能想要将一个 LED 连接到 GPIO 引脚 13,或者直接使用板载 LED 来完成这个目的。

要执行代码,请输入以下内容:

python VoiceRecognitionTest.py 

初始时,控制台显示*recording,说on

图片

语音识别—1

然后,在你说话之后,语音识别引擎将识别你所说的单词,从现有的语言模型中:

图片

语音识别—2

注意到显示的是on。这意味着语音识别引擎已经成功解码了我们刚才说的语音。同样,当我们通过麦克风说off时,另一个选项也会出现:

图片

语音识别—3

现在我们已经准备好了一个语音识别的概念证明。现在,我们将对这个概念进行一些小的修改,以锁定和解锁门。

基于语音命令的锁门/解锁

在本节中,我们将根据语音命令打开和关闭门。类似于前一个章节,我们使用如ONOFF之类的语音命令开关 LED,这里我们将使用伺服电机做类似的事情。主要目标是让读者理解英特尔爱迪生的核心概念,即我们使用语音命令执行不同的任务。可能会有人问,为什么我们使用伺服电机?

与普通直流电机不同,伺服电机可以旋转到操作者设定的特定角度。在正常情况下,控制门的锁定可能使用继电器。继电器的作用在第三章(3bd53219-a287-4d8f-9a58-5a06c5b14062.xhtml),英特尔爱迪生和物联网(智能家居自动化)中已有讨论。

让我们也探讨伺服电机的使用,以便我们可以扩大控制设备的范围。在这种情况下,当伺服电机设置为0度时,它是解锁的,当设置为90度时,它是锁定的。伺服电机的控制需要使用脉冲宽度调制引脚。英特尔爱迪生有四个 PWM 引脚:

伺服电机。图片来源:circuitdigest.com

操作伺服电机有三个操作线路:

  • Vcc

  • Gnd

  • 信号

典型的颜色编码如下:

  • 黑色——地线

  • 红色或棕色——电源

  • 黄色或白色——控制信号

我们使用的是 5V 伺服电机;因此爱迪生足以供电。爱迪生和伺服电机必须共享一个公共地。最后,信号引脚连接到英特尔爱迪生的 PWM 引脚。随着我们进一步进行这个小型项目,事情将变得更加清晰。

电路图

以下是为语音识别设计的电路图:

语音识别电路图

如前所述,伺服电机需要 PWM 引脚来操作,英特尔爱迪生总共有六个 PWM 引脚。在这里,我们使用数字引脚 6 进行伺服控制,数字引脚 13 用于 LED。至于外围设备,将您的 USB 声卡连接到英特尔爱迪生的 USB 端口,您就设置好了。

为 Python 配置伺服库

要控制伺服电机,我们需要通过 PWM 引脚发送一些信号。我们选择使用一个库来控制伺服电机。

使用以下链接从 GitHub 仓库获取Servo.py Python 脚本:

github.com/MakersTeam/Edison/blob/master/Python-Examples/Servo/Servo.py

下载文件并将其推送到你的爱迪生设备。之后,只需像执行 Python 脚本一样执行该文件:

python Servo.py

现在你已经完成了,你就可以使用 Python 和你的英特尔爱迪生使用伺服电机了。

回到硬件部分,伺服电机必须连接到数字引脚6,这是一个 PWM 引脚。让我们编写一个 Python 脚本来测试库是否正常工作:

from Servo import * 
import time 
myServo = Servo("Servo") 
myServo.attach(6) 
while True: 
   # From 0 to 180 degrees 
   for angle in range(0,180): 
         myServo.write(angle) 
         time.sleep(0.005) 
   # From 180 to 0 degrees 
   for angle in range(180,-1,-1): 
         myServo.write(angle) 
         time.sleep(0.005)             

上述代码基本上从0度扫到180度,然后再回到0度。电路与之前讨论的相同。最初,我们将伺服连接到伺服引脚。然后按照标准,我们将整个逻辑放在一个无限循环中。为了将伺服旋转到特定角度,我们使用.write(angle)。在两个 for 循环中,最初我们从0度旋转到180度,在第二个循环中,我们从180度旋转到0度。

还需要注意的是,time.sleep(time_interval)用于暂停代码一段时间。当你执行上述代码时,伺服应该旋转并回到初始位置。

现在,我们已经准备好了所有东西。我们只需将它们放在正确的位置,你的语音控制门就准备好了。最初,我们控制了一个 LED,然后我们学习了如何使用 Python 操作伺服。现在让我们使用 Sphinx 知识库工具创建一个语言模型。

语言模型

对于这个项目,我们将使用以下命令集。为了使事情简单,我们只使用两组命令:

  • 门打开

  • 门关闭

按照之前讨论的过程进行,创建一个文本文件并只写下三个独特的单词:

door open close 

保存它并将其上传到 Sphinx 知识库工具并编译它。

下载完压缩文件后,使用以下代码继续下一步:

import collections 
import mraa 
import os 
import sys 
import time 

# Import things for pocketsphinx 
import pyaudio 
import wave 
import pocketsphinx as ps 
import sphinxbase 
# Import for Servo  
from Servo import * 

led = mraa.Gpio(13)   
led.dir(mraa.DIR_OUT) 
myServo = Servo("First Servo") 
myServo.attach(6) 

print("Starting") 
while 1: 
         #PocketSphinx parameters 
         LMD   = "/home/root/Voice-Recognition-using-Intel-Edison/8578.lm" 
         DICTD = "/home/root/Voice-Recognition-using-Intel-Edison/8578.dic" 
         CHUNK = 1024 
         FORMAT = pyaudio.paInt16 
         CHANNELS = 1 
         RATE = 16000 
         RECORD_SECONDS = 3 
         PATH = 'Voice-Recognition-using-Intel-Edison' 
         p = pyaudio.PyAudio() 
         speech_rec = ps.Decoder(lm=LMD, dict=DICTD) 
         #Record audio 
         stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK) 
         print("* recording") 
         frames = [] 
         fori in range(0, int(RATE / CHUNK * RECORD_SECONDS)): 
               data = stream.read(CHUNK) 
               frames.append(data) 
         print("* done recording") 
         stream.stop_stream() 
         stream.close() 
         p.terminate() 
         # Write .wav file 
         fn = "test.wav" 
         #wf = wave.open(os.path.join(PATH, fn), 'wb') 
         wf = wave.open(fn, 'wb') 
         wf.setnchannels(CHANNELS) 
         wf.setsampwidth(p.get_sample_size(FORMAT)) 
         wf.setframerate(RATE) 
         wf.writeframes(b''.join(frames)) 
         wf.close() 

         # Decode speech 
         #wav_file = os.path.join(PATH, fn) 
         wav_file=fn 
         wav_file = file(wav_file,'rb') 
         wav_file.seek(44) 
         speech_rec.decode_raw(wav_file) 
         result = speech_rec.get_hyp() 
         recognised= result[0] 
         print("* LED section begins") 
         print(recognised) 
         ifrecognised == 'DOOR OPEN': 
               led.write(1) 
               myServo.write(90) 
         else: 
               led.write(0) 
               myServo.write(0) 
         cm = 'espeak "'+recognised+'"' 
         os.system(cm) 

上述代码与切换 LED 开关的代码大致相似。唯一的区别是,将伺服控制机制添加到现有代码中。在一个简单的 if else 块中,我们检查门打开门关闭条件。最后根据触发的内容,我们将 LED 和伺服设置到90度或0度位置。

使用英特尔爱迪生进行语音处理的结论

从之前讨论的项目中,我们探索了英特尔爱迪生的核心功能之一,并探索了通过语音控制英特尔爱迪生的新场景。一个流行的用例,实现了上述程序,可以是家庭自动化的案例,这在早期章节中已经实现。另一个用例是使用你的英特尔爱迪生构建一个虚拟语音助手。有多个机会可以使用基于语音的控制。这取决于读者的想象力,他们想探索什么。

在下一部分,我们将处理使用英特尔爱迪生进行图像处理的实现。

使用英特尔爱迪生进行图像处理

图像处理或计算机视觉是这样一个需要大量研究的领域。然而,我们在这里不会做火箭科学。我们选择了一个开源的计算机视觉库,称为OpenCVOpenCV支持多种语言,我们将使用 Python 作为我们的编程语言来执行人脸检测。

通常,图像处理应用程序有一个输入图像;我们处理输入图像,并得到一个处理后的输出图像。

Intel Edison 没有显示单元。因此,本质上我们首先将在我们的 PC 上运行 Python 脚本。然后,在 PC 上代码成功运行后,我们将修改代码以在 Edison 上运行。当进行实际实施时,一切将变得更加清晰。

我们的目标是执行人脸检测,并在检测到人脸时执行某些操作。

初始配置

初始配置将包括在 Edison 设备和 PC 上安装openCV包。

对于 PC,从www.python.org/downloads/windows/下载 Python。然后,在您的系统上安装 Python。同时,从sourceforge.net/projects/opencvlibrary/下载最新版本的 openCV。

下载 openCV 后,将提取的文件夹移动到C:\。然后,浏览到C:\opencv\build\python\2.7\x86

最后,将cv2.pyd文件复制到C:\Python27\Lib\site-packages

我们还需要安装numpy。Numpy 代表数值 Python。下载并安装它。

一旦安装了所有组件,我们需要测试是否一切安装正常。为此,打开 idle Python GUI 并输入以下内容:

importnumpy
import cv2 

如果没有错误发生,那么 PC 配置方面一切安装就绪。接下来,我们将为我们的设备进行配置。

要配置您的 Edison 与 openCV,最初执行以下操作:

opkg update
opkg upgrade

最后,在成功执行前面的操作后,运行以下命令:

opkg install python-numpy python-opencv

这应该会安装所有必要的组件。要检查是否一切设置就绪,请输入以下内容:

python 

按下Enter键。这应该进入 Python shell 模式。接下来,输入以下内容:

importnumpy
import cv2 

这是截图:

Python shell

如果没有返回任何错误消息,那么您已经准备就绪。

首先,我们将涵盖 PC 上的所有内容,然后我们将将其部署到 Intel Edison 上。

使用 OpenCV 进行实时视频显示

在我们继续进行人脸检测之前,让我们先看看我们是否可以访问我们的摄像头。为此,让我们编写一个非常简单的 Python 脚本来显示网络摄像头的视频流:

import cv2 

cap = cv2.VideoCapture(0) 

while(True): 
    # Capture frame-by-frame 
    ret, frame = cap.read() 

    # Our operations on the frame come here 
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 

    # Display the resulting frame 
    cv2.imshow('frame',gray) 
    if cv2.waitKey(1) & 0xFF == ord('q'): 
      break 

# When everything done, release the capture 
cap.release() 
cv2.destroyAllWindows() 

在前面的代码中,我们最初导入 openCV 模块为import cv2

接下来,我们初始化视频捕获设备并将索引设置为零,因为我们正在使用笔记本电脑附带的自带摄像头。对于桌面用户,您可能需要调整该参数。

初始化后,在一个无限循环中,我们使用cap.read()逐帧读取传入的视频帧:

ret, frame = cap.read()

接下来,我们对传入的视频流应用一些操作。在这个示例中,我们将 RGB 视频帧转换为灰度图像:

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

最后,帧将在一个单独的窗口中显示:

if cv2.waitKey(1) & 0xFF == ord('q'):
break

在前面的两行中,我们实现了键盘中断机制。当有人按下q或按下Esc键时,显示将关闭。

一旦你获取到传入的视频流,我们就准备好进行面部检测。

面部检测理论

面部检测是对象识别的一个非常具体的情况。有许多面部识别的方法。然而,我们将讨论这里给出的两种:

  • 基于颜色的分割

  • 基于特征的识别

基于颜色的分割

在这项技术中,面部是基于肤色进行分割的。输入通常是 RGB 图像,而在处理阶段我们将其转换为色调饱和度值HSV)或 YIQ(亮度(Y),同相正交)颜色格式。在这个过程中,每个像素被分类为肤色像素或非肤色像素。使用除 RGB 之外的其他颜色模型的原因是,有时 RGB 无法在不同光照条件下区分肤色。使用其他颜色模型可以显著提高这一点。

此算法在此处不会使用。

基于特征的识别

在这项技术中,我们针对某些特征进行识别。使用基于 Haar 特征的级联进行面部检测是 Paul Viola 和 Michael Jones 在 2001 年发表的论文"使用简单特征的快速对象检测"中提出的一种有效对象检测方法。这是一种基于机器学习的方法,其中级联函数针对一组正面和负面的图像进行训练。然后它被用来检测其他图像中的对象。

算法最初需要大量的正面图像。在我们的案例中,这些是面部图像,而负面的图像则不包含面部图像。然后我们需要从中提取特征。

为了这个目的,以下图所示的 Haar 特征被使用。每个特征都是通过从白色矩形下的像素总和减去黑色矩形下的像素总和得到的单个值:

Haar 特征

Haar 分类器需要针对面部、眼睛、微笑等进行训练。OpenCV 包含一系列预定义的分类器。它们位于C:\opencv\build\etc\haarcascades文件夹中。既然我们知道如何进行面部检测,我们将使用预训练的 Haar 分类器进行面部检测。

面部检测的代码

以下是为面部检测的代码:

import cv2 
import sys 
import os 

faceCascade = cv2.CascadeClassifier('C:/opencv/build/haarcascade_frontalface_default.xml') 
video_capture = cv2.VideoCapture(0) 
while (1): 
    # Capture frame-by-frame 
    ret, frame = video_capture.read() 

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 
    faces = faceCascade.detectMultiScale(gray, 1.3, 5)  
    # Draw a rectangle around the faces 

    for (x, y, w, h) in faces: 
      cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) 

    # Display the resulting frame 
    cv2.imshow('Video', frame) 

    if cv2.waitKey(25) == 27: 
      video_capture.release() 
      break 

# When everything is done, release the capture 
video_capture.release() 
cv2.destroyAllWindows() 

让我们逐行查看代码:

import cv2 
import sys 
import os 

导入所有必需的模块:

faceCascade = cv2.CascadeClassifier('C:/opencv/build/haarcascade_frontalface_default.xml') 
video_capture = cv2.VideoCapture(0) 

我们选择级联分类器文件。同时,我们选择视频捕获设备。确保你正确地提到了路径:

ret, frame = video_capture.read() 
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 

在前面的行中,位于无限循环内部,我们读取视频帧并将其从 RGB 转换为灰度:

faces = faceCascade.detectMultiScale(gray, 1.3, 5)  

前一行是代码中最重要的部分。我们实际上已经对传入的流应用了操作。

detectMultiScale包含三个重要参数。这是一个用于检测图像的通用函数,因为我们正在应用面部 Haar 级联,所以我们正在检测面部:

  • 第一个参数是需要处理的输入图像。这里我们传递了原始图像的灰度版本。

  • 第二个参数是缩放因子,它为我们提供了创建缩放金字塔的因子。通常,大约 1.01-1.5 是合适的。值越高,速度越快,但准确性降低。

  • 第三个参数是minNeighbours,它影响检测区域的质量。较高的值会导致检测减少。3-6 的范围是好的:

      # Draw a rectangle around the faces     
      for (x, y, w, h) in faces: 
      cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) 

前面的行只是简单地围绕人脸绘制矩形。

最后,我们显示结果帧,并使用键盘中断来释放视频捕获设备并销毁窗口。

现在按F5运行代码。最初,它将要求保存文件,然后执行开始:

检测到人脸的图像窗口截图

到目前为止,如果一切操作都按正确的方式进行,你一定对人脸检测及其如何使用 OpenCV 实现有了大致的了解。但现在,我们需要将其转移到 Intel Edison 上。同时,我们还需要修改某些部分以适应设备的性能,因为它没有显示单元,最重要的是它只有 1GB 的 RAM。

Intel Edison 代码

对于 Intel Edison,让我们找出实际上可以实现什么。我们没有显示屏,所以我们可以仅依赖控制台消息和 LED,也许,用于视觉信号。接下来,我们可能需要优化代码以在 Intel Edison 上运行。但首先,让我们编辑之前讨论的代码,以包括 LED 和一些类型的消息到图片中:

import cv2 
import numpy as np 
import sys 
import os 

faceCascade = cv2.CascadeClassifier('C:/opencv/build/haarcascade_frontalface_default.xml') 
video_capture = cv2.VideoCapture(0) 
led = mraa.Gpio(13)   
led.dir(mraa.DIR_OUT) 
while (1): 
led.write(0) 
    # Capture frame-by-frame 
    ret, frame = video_capture.read() 

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 
    faces = faceCascade.detectMultiScale(gray, 2, 4) 
    iflen(faces) > 0: 
      print("Detected") 
        led.write(1) 
    else: 
      print("You are clear to proceed") 
        led.write(0) 
    if cv2.waitKey(25) == 27: 
      video_capture.release() 
      break 

# When everything is done, release the capture 
video_capture.release() 
cv2.destroyAllWindows() 

由于 Intel Edison 只有一个 USB 端口,因此我们已将cv2.VideoCapture的参数指定为0。同时请注意以下行:

faces = faceCascade.detectMultiScale(gray, 2, 4) 

你会注意到参数已经被更改以优化它们在 Intel Edison 上的性能。你可以轻松地调整参数以获得良好的结果。

我们为 LED 添加了一些开关行:

使用 openCV 在图像中进行人脸检测的控制台输出

这时你开始注意到,由于 RAM 的限制,Intel Edison 根本不适合图像处理。

现在你正在处理高处理应用时,我们不能仅依赖 Intel Edison 的处理能力。

在这些情况下,我们选择基于云的解决方案。对于基于云的解决方案,存在多个框架。其中之一是微软的 Project Oxford(www.microsoft.com/cognitive-services)。

微软认知服务为我们提供了人脸检测、识别、语音识别等多个 API。使用前面的链接了解更多信息。

经过本章的所有讨论,我们现在知道语音识别表现相当不错。然而,图像处理方面并不理想。但我们为什么关注使用它呢?答案在于英特尔爱迪生确实可以用作图像收集设备,而其他处理可以在云端进行:

图片

一瞥基于安全的系统架构

处理可以在设备端或云端进行。这完全取决于用例和资源的可用性。

读者开放性任务

本章的任务可能需要一些时间,但最终结果将会非常出色。实现微软认知服务以执行面部识别。使用爱迪生从用户那里收集数据并将其发送到服务进行处理,并根据结果执行一些操作。

摘要

在本章中,我们学习了使用英特尔爱迪生进行语音识别的一些技术。我们还学习了如何在 Python 中执行图像处理,并在英特尔爱迪生上实现了相同的功能。最后,我们探讨了现实生活中的基于安全的系统会是什么样子,以及微软认知服务的一个开放性问题。

第五章,《使用英特尔爱迪生的自主机器人》,将完全致力于机器人技术以及如何使用英特尔爱迪生与机器人结合。我们将涵盖自主和手动机器人技术。

第五章:使用英特尔爱迪生进行自主机器人

机器人工程是工程的一个分支,涉及设计、开发和

机器人的应用。虽然机器人可以采取人类的形式,但大多数机器人都是

设计来执行一组特定的任务,它可能看起来就像一台机器或

任何非人类的东西。我们感兴趣的是英特尔爱迪生如何被用来

开发机器人。本章将涵盖机器人的自主方面,并将主要

将涵盖以下主题:

  • 机器人系统的架构

  • 英特尔爱迪生作为控制器

  • 将传感器与英特尔爱迪生连接

  • 使用实时环境校准传感器

  • 执行器:电机、伺服电机等

  • 电机驱动器:双 H 桥配置

  • 速度控制

  • 将一切整合:循线机器人

  • 基于 PID 控制系统的更高级循线机器人

本章将处理循线机器人,并探讨其所有组件,并讨论一些高级循线的技巧和窍门。本章中的所有代码都将使用 Arduino IDE 编写。

典型机器人系统的架构

在典型的自主机器人系统中,传感器负责收集数据。基于这些数据,控制器最初对其进行处理,然后通过使用执行器执行动作:

机器人系统的架构

我们还可以有一个仪表盘,它可能只是显示正在发生的事情,或者有时可以向机器人提供一些命令。架构的模型有很多种。前面的架构是这样的,机器人的组件被认为是水平组织的。通过传感器收集的信息在执行动作之前需要经过多个步骤。除了水平流动之外,还可能有垂直流动,控制可以在整个流程完成之前随时转移到执行器。这就像高速公路上行驶的汽车队列,然后一辆车驶入出口匝道并离开。我们将开发的循线机器人将使用水平方法,这是最原始的方法。

英特尔爱迪生作为控制器

在整本书中,我们已经看到了英特尔爱迪生在各个应用中的使用。在每种情况下,我们都强调其作为控制器的作用,因为其特性。现在,在机器人领域,我们再次强调其作为微控制器的作用,但问题是,为什么?

好吧,当我们处理机器人时,在某些情况下,我们可能需要英特尔爱迪生的核心功能。英特尔爱迪生内置了蓝牙低功耗(BLE)、Wi-Fi 以及一些使其非常适合用于机器人的特性。Arduino 兼容的扩展板允许我们使用通用的 Arduino 外设板和电机驱动器,而核心功能为机器人提供了一些额外的功能,例如基于语音的命令和将数据上传到云端。

所有这些都在一个单元中。因此,我们不需要使用多个外围设备,英特尔 Edison 为我们提供了在单一屋檐下使用所有设备的灵活性。能够附加摄像头也为其增添了更多风味:

图片

英特尔蜘蛛机器人。它使用英特尔 Edison 和定制的扩展板来控制伺服系统。图片来源:i.ytimg.com/vi/3NeJisPvHcU/maxresdefault.jpg

将传感器连接到 Intel Edison

在第二章 气象站(物联网)中,我们简要讨论了传感器。在这里,我们更关注那些帮助机器人确定其在环境中的确切位置的传感器。我们将处理两个传感器以及如何校准它们:

  • 超声波传感器(HCSR04)

  • 用于检测线条的红外传感器

使用前面传感器的理由是我们将这些传感器用于机器人技术。然而,其他传感器也被使用,但这些是最常用的。现在下一个问题是“它们是如何操作的,我们如何将它们连接到英特尔 Edison?”

让我们看看它们中的每一个。

超声波传感器(HCSR04)

使用此传感器的主要目的是测量距离。在我们的情况下可能不需要,但在初学者机器人技术中非常有用:

图片

超声波传感器 HCSR04

它发出超声波,如果有物体存在,则反射并被接收器接收。根据脉冲输出时间和接收到的脉冲,我们计算距离。

该传感器可以提供从 2 厘米到 400 厘米的读数。它包含发射器和接收器,因此我们可以直接将我们的 Intel Edison 插入并使用。工作电压为+5V 直流。操作不受阳光或深色材料的影响,因此非常适合寻找距离。需要注意的是,光束角度为 15 度:

图片

光束角度

让我们看看使用前面模块的一些示例代码。

从传感器直接计算距离没有直接的方法。我们需要使用两个 GPIO 引脚。第一个将是触发引脚,它将被配置为输出,而第二个将是回声引脚,它将处于输入模式。当我们通过触发引脚发送脉冲时,我们等待在回声引脚上接收到的脉冲。时间差和一些数学计算给出了距离:

inttrigPin=2; 
intechoPin=3; 
void setup()  
  { 
    pinMode(trigPin,OUTPUT); 
    pinMode(echoPin, INPUT); 
    Serial.begin(9600); 
  } 
void loop()  
  { 
    long duration, distance; 
    //Send pulses 
    digitalWrite(trigPin, LOW); 
    delayMicroseconds(2); 
    digitalWrite(trigPin, HIGH); 
    delayMicroseconds(10); 
    digitalWrite(trigPin, LOW); 

    //Receive pulses 
    pinMode(echoPin, INPUT); 
    duration = pulseIn(echoPin, HIGH); 

    //Calculation 
    distance = microsecondsToCentimeters(duration); 
    Serial.println(distance); 
    delay(200); 
  } 

longmicrosecondsToCentimeters(long microseconds) 
  { 
    return microseconds / 29 / 2; 
  } 

在前面的代码中,关注循环方法。最初,我们发送一个低脉冲以获得干净的高脉冲。接下来,我们发送一个高脉冲10秒。最后,我们在回波引脚上获得高脉冲,并使用pulseIn()方法来计算持续时间。持续时间被发送到另一个方法,在那里我们使用已知的声速参数来计算距离。要在 Edison 上使用此代码,请按照以下电路图将 HCSR04 传感器连接到 Edison:

电路图

使用前面的电路连接您的 HCSR04 到您的 Edison。接下来,上传代码并打开串行监视器:

HCSR04 的串行监视器读数

前面的截图显示了我们的串行监视器,我们在其中获得了测量距离的读数。

HCSR04 的应用

此传感器有众多应用,尤其是在自主机器人领域。甚至可以使用此传感器完成地图绘制。当 HCSR04 放置在伺服电机顶部时,HCSR04 可以用来绘制整个 360 度的地图。这些在我们要执行同时定位与地图构建SLAM)时非常有用。

红外传感器

这些传感器有多种用途。从线检测到边缘检测,这些传感器甚至可以优化为用作接近传感器。甚至我们的智能手机也使用这些传感器。它们通常位于前置扬声器附近。这些传感器也基于发送和接收信号的原则工作。

工作方法

以下图像是常用的红外传感器:

红外传感器:图片来源:www.amazon.in/Robosoft-Systems-Single-Sensor-Module/dp/B00U3LKGTG

它有一个发射器和接收器。输出可以是数字或模拟。发射器发送红外波。基于物体,它被反射回来,信号被接收器接收。基于此,我们知道我们附近有东西。

红外传感器有多个应用:

  • 检测近距离物体

  • 测量温度

  • 红外相机

  • 被动红外运动检测

第一个已经讨论过了。第二个是使用红外探测器来检测红外光谱,基于此,我们计算温度。红外相机在军事、消防和许多我们需要从远处知道温度很高的地方得到广泛应用。第三个是 PIR。被动红外模块用于运动检测,并用于家庭自动化。

红外传感器的数字和模拟输出

我们已经提到,这些传感器要么提供数字输出,要么提供模拟输出。但我们为什么如此关注这一点呢?主要是因为在典型的循线中,我们可能使用数字值,但在高速循线机器人中,我们选择模拟值。最大的优点是,从白色表面到黑色表面的过渡将更加平滑,在高速循线中,我们根据传感器的模拟输入控制电机的速度,因此具有更有效的控制。对于简单用途,我们选择数字输出。

红外传感器模块的校准

红外传感器模块需要校准的原因如下:

  • 环境可能对传感器来说过于明亮,以至于无法实际检测到任何东西。

  • 由于环境参数的变化,我们可能需要对其进行校准以满足我们的需求。

让我们考虑在两种条件下使用循线机器人:

  • 在正常照明的环境中(室内)

  • 在阳光明媚的环境中(户外)

当机器人在室内环境下的环境光下高效运行时,并不一定意味着它会在户外做同样的事情。原因是传感器是红外传感器,在明亮的环境中,尤其是在阳光下,检测会更困难。那么,我们如何校准传感器呢?每个传感器模块都有一个电位器,用于控制传感器的灵敏度。这个电位器需要根据周围环境进行调整,以便获得适当的读数。

现在,当你处理机器人时,请随身携带一些代码,因为可能需要。同样,对于校准,我们遵循非常简单的步骤。

让我们考虑一个简单的循线使用案例。我们需要跟随白色轨道上的黑色线条。步骤如下:

  1. 将机器人或传感器放置在理想位置(理想位置指的是机器人需要所在的位置,即线的中心)。

  2. 如果机器人尚未构建,请用纸覆盖传感器,并将其保持在或大约 2 厘米高于轨道的位置。

  3. 现在,检查传感器的值,代码将在以下步骤中展示。

  4. 白色线条上方的传感器和黑色线条上方的传感器之间应该有明显的差异。如果你两个传感器都在白色线条上方,那么它们的值应该完全相同,否则调整电位器。

  5. 同样,通过将两个传感器都放置在黑色线条上方来执行此过程。

  6. 如果环境或更确切地说,照明条件发生变化,请再次执行整个流程。

校准和传感器读数的硬件设置

按照以下电路图进行硬件设置:

图片

传感器校准电路

电路相当直接且易于理解。只需将两个传感器连接到A0A1引脚,以及公共的VccGnd连接。

现在,让我们继续编写代码:

void setup()  
  { 
    Serial.begin(9600); 
  } 
void loop()  
  { 
    int a= analogRead(A0); 
    int b= analogRead(A1); 
    Serial.print(a); 
    Serial.print("       "); 
    Serial.println(b); 
    delay(200); 
  } 

上述代码返回了从传感器获取的值和读数。在将代码烧录到英特尔爱迪生之后,你将开始接收传感器的值。现在,需要按照校准中提到的测试进行测试以校准传感器。

执行器 - 直流电机和伺服电机

在每一个机器人项目中,机器人都需要获得一些移动性。为了移动性,我们使用直流电机或伺服电机。本节将讨论电机和电机类型。下一节将讨论如何使用英特尔爱迪生控制电机。

电动机是机电装置,将电能转换为机械能。电动机中的两个电气元件是磁场绕组和电枢。它是一个两极装置。电枢通常位于转子,而磁场绕组通常位于定子。

永磁电机使用永磁体提供磁场通量:

图片

永磁电机

这些电机受其能驱动的负载限制,这是一个缺点。然而,考虑到我们专注于机器人和小规模应用,我们选择了这种类型的电机。

这些电机通常在 9V-12V 电压下运行,最大满载电流约为 2-3 A。在机器人技术中,我们主要处理三种类型的直流电机:

  • 恒定直流

  • 步进电机

  • 伺服电机

恒定直流电机是连续旋转装置。当电源开启时,电机旋转,如果极性反转,则反向旋转。这些电机广泛应用于为机器人提供移动性。通常,直流电机带有齿轮,这提供了更多的扭矩:

图片

直流电机。图片来源:img.directindustry.com/images_di/photo-g/61070-3667645.jpg

下一个类别是伺服电机。这些电机的速度通过脉冲宽度调制技术,即通过改变脉冲宽度来控制。伺服电机通常由四个部分组成:直流电机、齿轮组、控制电路和位置传感器(通常是电位计)。通常,伺服电机包含三组线:Vcc、Gnd 和信号。由于其精确性,伺服电机有完全不同的应用场景,其中位置是一个重要参数。伺服电机不像标准直流电机那样自由旋转。相反,旋转角度限制在 180 度(或更多)的往返之间。控制信号以脉冲宽度调制(PWM)信号的形式出现:

图片

伺服电机:图片来源:electrosome.com/wp-content/uploads/2012/06/Servo-Motor.gif

最后,步进电机是伺服电机的先进形式。它们提供完整的 360 度旋转,并且是连续运动。步进电机利用围绕中心齿轮排列的多个齿形电磁铁来定义位置。它们需要一个外部控制器电路来单独激励每个电磁铁。步进电机有两种类型:单极和双极。双极电机是步进电机中最强的一种,通常有四个或八个引脚:

步进电机

在跟随线项目里,我们将处理直流电机。但是电机不能直接连接到英特尔爱迪生,因此我们需要一个接口电路,通常被称为电机驱动器。

电机驱动器

由于电机消耗的电压和电流不能仅由 GPIO 引脚提供,我们选择使用电机驱动器。这些驱动器有外部电源,并使用微控制器的 GPIO 引脚接收控制信号。根据接收到的控制信号,电机以特定的速度旋转。市场上有很多电机驱动器;我们最初将专注于 L293D,然后是定制和高功率驱动器。

任何电机驱动器的目标是从控制器这里,即英特尔爱迪生,接收控制信号,并将最终输出发送到电机。通常,电机驱动器可以控制电机在两个方向上旋转,也可以控制速度。

L293D

L293D 是一种典型的电机驱动集成电路,可以驱动两个电机在两个方向上。它就像是每个机器人项目的启动器。它是一个 16 位 IC:

L293D 的引脚排列。图片来源:www.gadgetronicx.com

Vs 电机供电的最大电压是 36V。它可以为每个通道提供最大 600mA 的电流。

它基于 H 桥的概念。该电路允许电流在两个方向上流动。我们先看看 H 桥电路:

H 桥电路的简单布局。图片来源:en.wikipedia.org/

在这里,S1S2S3S4是开关,在现实生活中包含晶体管。操作极其简单。当S1S4打开时,中心电机向一个方向旋转,而当S2S3打开时,则发生相反的情况。S1S2S3S4从微控制器接收控制信号,并相应地操作电机的方向。

L293D 由两个这样的电路组成。因此,我们可以控制多达两个电机。可以将 L293D 用作模块或仅作为独立的 IC。通常我们只需要担心四个引脚,我们将在这里发送控制信号。我们有 L293D 的引脚布局。让我们看看哪些信号会导致什么样的动作。

引脚 1-8 负责一个电机,而引脚 9-16 负责另一个。

使能引脚设置为逻辑高以进行操作。另一侧也是如此。

需要调整的是输入引脚 2、7、10 和 15。电机连接到引脚 3、6、11 和 14。Vss 是电机电源,而 Vss 或 Vcc 是内部电源。当使能 1 和使能 2,即引脚 1 和 9,根据条件设置为高时:

引脚 2 或 10 引脚 7 或 15 电机
顺时针
停止
停止
逆时针

前面的表格总结了基于输入触发的动作。需要注意的是,如果两个输入引脚都开启或关闭,则电机不会旋转。在典型的 L293D 模块中,只有四个控制引脚和主要电压供电及 Gnd 引脚是暴露的。在前面的表格中,提到的是 2 或 107 或 15。这对引脚是 2 和 7 或 10 和 15。这意味着 2 是开启或 10 是关闭,其他引脚同理。电机的运动,即指定为 顺时针逆时针,取决于电机的连接。假设当控制信号改变时,旋转方向会反转。

电路图

构建以下临时电路以测试电机。使能引脚应连接到英特尔爱迪生的 5V,而 Gnd 连接到 Gnd

建议使用电机驱动模块而不是独立的 IC。这使我们能够以更简单的方式完成任务。

CP1CP2 是第一台电机的控制引脚,而 CP3CP4 是第二台电机的控制引脚:

电机测试电路图

在处理机器人技术时,建议保留可以测试您的电机驱动器是否正常工作的示例代码。这里讨论的代码将解释如何做到这一点。

这部分特别重要,因为它是作为电机测试单元和校准所必需的。此代码也取决于您的电机连接,可能需要尝试和错误:

#define M11 2 
#define M12 3 
#define M21 4 
#define M22 5 
void setup()  
  { 
    pinMode(M11,OUTPUT); 
    pinMode(M12,OUTPUT); 
    pinMode(M21,OUTPUT); 
    pinMode(M22,OUTPUT); 
  } 
void loop()  
  { 
    forward(); 
    delay(10000); 
    backward(); 
    delay(10000); 
    left(); 
    delay(10000); 
    right(); 
    delay(10000); 
    stop(); 
    delay(10000); 
  } 
void forward() 
  { 
    digitalWrite(M11,HIGH); 
    digitalWrite(M12,LOW); 
    digitalWrite(M21,HIGH); 
    digitalWrite(M22,LOW); 
  } 
void backward() 
  { 
    digitalWrite(M11,LOW); 
    digitalWrite(M12,HIGH); 
    digitalWrite(M21,LOW); 
    digitalWrite(M22,HIGH); 
  } 
void right() 
  { 
    digitalWrite(M11,HIGH); 
    digitalWrite(M12,LOW); 
    digitalWrite(M21,LOW); 
    digitalWrite(M22,HIGH); 
  } 
void left() 
  { 
    digitalWrite(M11,LOW); 
    digitalWrite(M12,HIGH); 
    digitalWrite(M21,HIGH); 
    digitalWrite(M22,LOW); 
  } 
void stop() 
  { 
    digitalWrite(M11,LOW); 
    digitalWrite(M12,LOW); 
    digitalWrite(M21,LOW); 
    digitalWrite(M22,LOW); 
  } 

前面的代码非常简单易懂。在这里,我们将它分解为几个方法,用于 前进后退左转右转停止。这些方法将负责通过 digitalWrite 向电机驱动器发送控制信号。需要注意的是,此代码可能无法正常工作。这完全取决于您的电机如何连接。因此,当您将此代码烧录到英特尔爱迪生时,请记录电机在前 10 秒内的旋转方向。如果两个电机以相同方向旋转,那么您很幸运,不需要调整连接。然而,如果您观察到相反方向的旋转,那么请将其中一个电机的连接从其电机驱动器中反转。这将允许电机以相反方向旋转,因此两个电机将以相同方向旋转。

另一个需要注意的重要点是,考虑到leftright方法,我们注意到电机以不同的方向旋转。这就是机器人转向系统实现的方式。它将在本章的后续部分讨论。

在处理电机驱动器时,请首先查阅规格说明。然后进行连接和编码。

直流电机速度控制

现在我们知道了如何控制电机及其旋转方向,让我们看看如何控制电机的速度,这对于高级操控是必要的。

速度控制是通过 PWM 技术实现的,其中我们通过改变脉冲宽度来控制速度。

这种技术用于通过数字方式获得模拟结果。英特尔爱迪生的数字引脚产生方波:

典型方波

开关模式可以模拟从全开5V到全关0V之间的电压。这是通过改变信号处于开启和关闭状态的时间来实现的。开启时间的持续时间称为脉冲宽度。为了获得变化的脉冲值,我们改变或调制脉冲宽度。如果这样做足够快,那么结果就是 0-5V 之间的一个值。

在 Arduino 扩展板上为英特尔爱迪生提供了 PWM 引脚。这些引脚被用于:

PWM 样本。图片来源:www.arduino.cc

现在我们可以实现这个来控制我们自己的电机的速度。在先前的 L293D IC 中,使能引脚可以用作 PWM 输入。

L293D 模块主要暴露以下引脚:

  • 电机 1 的输入 1

  • 电机 1 的输入 2

  • 电机 2 的输入 1

  • 电机 2 的输入 2

  • 使能 1 的电机

  • 使能 2 的电机

  • Vcc

  • Gnd

看看以下模块:

L293D 电机驱动模块:

如前所述,总共暴露了八个引脚。

将使能引脚连接到英特尔爱迪生上的任何 PWM 引脚:

  • 使能 1 连接到数字引脚 6

  • 使能 2 连接到数字引脚 9

接下来,为了控制速度,我们需要在英特尔爱迪生上那些已启用的 PWM 引脚中使用analogWrite方法。

要设置英特尔爱迪生 PWM 控制的频率,请使用以下链接中的示例并克隆它:

github.com/MakersTeam/Edison/blob/master/Arduino-Examples/setPWM_Edison.ino

analogWrite方法的值范围是 0-255,其中 0 始终是关闭的,255 始终是开启的。

现在用这个修改前面的代码来设置使能引脚的值。这里展示了如何使用它的一个例子。在完全有动力的运动中控制引脚的任务留给读者:

#define M11 2 
#define M12 3 
#define M21 4 
#define M22 5 
#define PWM 6 
void setup()  
  { 
    pinMode(M11,OUTPUT); 
    pinMode(M12,OUTPUT); 
    pinMode(M21,OUTPUT); 
    pinMode(M22,OUTPUT); 
  } 
void loop()  
  { 
    forward(); 
  } 
void forward() 
  { 
    digitalWrite(M11,HIGH); 
    digitalWrite(M12,LOW); 
    digitalWrite(M21,HIGH); 
    digitalWrite(M22,LOW); 
    analogWrite(PWM, 122); 
  } 

在前面的代码中,强调forward方法,我们使用了analogWrite (PWM, 122)。这意味着电机现在应该以原始速度的一半旋转。这种技术可用于更快的线跟踪机器人速度控制。

更高级的电机驱动器

在处理机器人技术时,可能会遇到一些情况,由于 L293D 的电流限制,它并不是一个好的选择。在这些情况下,我们选择更强大的驱动器。让我们看看 robokits 的另一个产品,它可以驱动高扭矩的电机:

图片

双电机驱动高功率。图片来源:robokits.co.in/motor-drives/dual-dc-motor-driver-20a

前面的电机驱动器是我个人的最爱。它具有多个控制功能,可以驱动高扭矩电机。驱动器有以下五个控制引脚:

  • Gnd: 地线。

  • DIR: 当低电平时,电机朝一个方向旋转;当高电平时,它朝另一个方向旋转。

  • PWM: 脉宽调制以控制电机的速度。推荐频率范围是 20 Hz - 400 Hz。

  • BRK: 当处于高电平时,它将停止电机的运行。

  • 5V: 电机驱动板提供的调节后的 5V 输出。

从这里讨论的引脚描述中,应该很清楚为什么这是一个更好的选择。

电压和电流规格如下:

  • 电压范围:6V 至 18V

  • 最大电流:20 A

电流和电压规格帮助我们以最大负载驱动电机。我们已经在许多应用中使用了这个电机驱动器,并且它始终如一地提供服务。然而,市场上也有其他可以提供类似功能的电机驱动器。选择使用哪种电机驱动器取决于某些因素,如本节所讨论的:

  • 功率: 这完全取决于电机在满载下运行所需的功率。满载和空载条件下的电流。如果你打算使用 L293D 的高扭矩电机驱动器,你可能会烧毁你的电机驱动器。

  • 操控: 根据问题的使用案例,电机选择由你决定,最终是电机驱动器的选择。在高速线跟踪中,我们需要 PWM 功能,因此我们需要一个能够处理 PWM 信号的驱动器。

最终,根据你的使用案例选择你的电机驱动器。

下图是使用我们之前开发的电机驱动器的小型高性能 UGV 的图片:

图片

Black-e-Track UGV。UGV 的电机是高扭矩 300 RPM,可以爬上高达 75 度的陡坡。

现在我们对电机驱动器的工作原理以及如何选择一个好的电机驱动器有了相当好的了解。

线跟踪机器人(整合所有内容)

根据本章前面的部分,我们已经对如何将所有内容整合到一个平台上有了相当好的了解。让我们深入了解步骤,看看线跟踪机器人是如何工作的。

线跟踪的基本概念

下图展示了线跟踪机器人的概念:

图片

线跟踪概念

在前一个图中,我们有两种情况。第一种是当左侧传感器位于线条上时,第二种情况与右侧传感器相同。黑色线条是机器人应该跟随的轨道。有传感器用方块表示。让我们考虑前一个图的左侧。

初始时,两个传感器都位于前一个图中的位置 1 上的白色表面上。接下来,考虑图像左侧的位置 2。左侧传感器覆盖黑色线条,传感器被激活。基于此,我们拦截传感器读数并将控制信号中继到电机驱动器。在前一个案例中,针对图像的左侧,我们得到左侧传感器检测到线条,因此机器人不应该向前移动,而应该轻微向左转弯,直到两个传感器返回相同的值。

图的右侧情况也是如此,右侧传感器检测到黑色线条,并触发运动执行命令。这是一个简单的跟随线条的机器人案例,需要跟随单色线条。如果需要跟随多色线条,情况会有所不同。

既然我们已经知道了跟随线条的确切过程,现在我们可以更多地关注机器人如何执行转弯以及机器人结构。

机器人运动执行

为了理解机器人如何执行其运动,让我们考虑一个四轮驱动机器人:

图片

典型的 4WD 机器人结构

机器人可以跟随基于差速驱动的系统,其中转弯是在机器人转弯的一侧执行,该侧的轮子以相反方向旋转,或减速,或停止。如果你处理的是两个轮子,停止一侧并旋转另一侧也可以。然而,如果你使用四个轮子,那么始终选择反向旋转是安全的,否则会导致轮胎打滑。再次强调,当使用两个轮子时,我们选择使用万向轮,以保持机器人平衡。轮胎打滑条件和其他结构元素的详细信息将在第六章,使用 Intel Edison 的机器人手册中讨论。

跟随线条机器人所需的硬件清单

简单两轮驱动跟随线条机器人所需的硬件清单如下:

  • 机器人底盘

  • 9V 直流电机

  • 电机驱动器 L293D

  • 两个红外传感器

  • 9V 直流电源

  • 两个轮子

  • 一个万向轮

  • Intel Edison,用作控制器

将电机连接到底盘和万向轮的过程将不会展示。将展示电路图,所有组件的排列取决于读者。

使用两轮或四轮驱动机器人底盘,由于我们使用两个轮子,因此将在机器人前方安装万向轮。传感器应位于机器人前部,两侧。让我们考虑一个 2D 模型:

图片

典型线跟踪机器人的 2D 机器人模型

前面的图是 2WD 线跟踪机器人的 2D 模型。传感器应位于两侧,万向轮位于中间。虽然 L293D 和 Intel Edison 可以放置在任何位置,但万向轮、传感器、电机以及显然是轮子的位置应该与前面图中所示的结构相同或相似。

确保传感器到地面的距离对于检测线条是最佳的。这个距离必须在传感器的校准期间计算。

硬件设置通常需要一些时间,因为它涉及到很多电线处理和松动的焊接接头通常会带来更多问题。现在,在继续编写代码之前,让我们使用以下电路图将所有东西连接起来:

图片

线跟踪机器人的电路图

前面的电路是我们在本章中迄今为止所做内容的组合。创建了一个公共地线、一个Vcc线路,它连接了L293DIntel Edison传感器9V DC电源为电机供电,而控制引脚负责从 Intel Edison 发送控制信号。传感器的输出连接到 Intel Edison 的数字引脚。最后,电机驱动器根据控制信号控制电机。

如果你仔细观察前面的电路图,那么所有内容都符合典型的机器人架构:

图片

机器人架构

但仪表盘缺失。我们可能添加仪表盘,但到目前为止,我们对此不感兴趣。

现在硬件部分已经完成所有连接和电路,让我们添加代码让它运行。

算法非常简单,如下所示:

  1. 检查左传感器值。

  2. 如果检测到,向转。

  3. 否则,检查传感器值。

  4. 如果检测到,向转。

  5. 否则如果都检测到。

  6. 停止运动。

  7. 否则,就向前移动前进

下面的代码是针对这个的:

#define M11 2 
#define M12 3 
#define M21 4 
#define M22 5 
#define S1 7 
#define S2 8 
inta,b; 
void setup()  
  { 
    pinMode(M11,OUTPUT); 
    pinMode(M12,OUTPUT); 
    pinMode(M21,OUTPUT); 
    pinMode(M22,OUTPUT); 
    pinMode(S1,INPUT); 
    pinMode(S2,INPUT); 
  } 
void loop()  
  { 
    a=digitalRead(S1); 
    b=digitalRead(S2); 
    //Left turn condition 
    if(a==1&&b==0) 
      { 
        left(); 
      } 
    //Right turn condition 
    else if(a==0&&b==1) 
      { 
        right(); 
      } 
    //Stop condition 
    else if(a==1&&b==1) 
      { 
        stop(); 
      } 
    //By default forward 
    else 
      { 
        forward(); 
      } 
  } 
void forward() 
  { 
    digitalWrite(M11,HIGH); 
    digitalWrite(M12,LOW); 
    digitalWrite(M21,HIGH); 
    digitalWrite(M22,LOW); 
  } 
void backward() 
  { 
    digitalWrite(M11,LOW); 
    digitalWrite(M12,HIGH); 
    digitalWrite(M21,LOW); 
    digitalWrite(M22,HIGH); 
  } 
void right() 
  { 
    digitalWrite(M11,HIGH); 
    digitalWrite(M12,LOW); 
    digitalWrite(M21,LOW); 
    digitalWrite(M22,HIGH); 
  } 
void left() 
  { 
    digitalWrite(M11,LOW); 
    digitalWrite(M12,HIGH); 
    digitalWrite(M21,HIGH); 
    digitalWrite(M22,LOW); 
  } 
void stop() 
  { 
    digitalWrite(M11,LOW); 
    digitalWrite(M12,LOW); 
    digitalWrite(M21,LOW); 
    digitalWrite(M22,LOW); 
  } 

在前面的代码中,它与电机测试的代码非常相似,只是将void loop()替换为算法中描述的主要逻辑。我们使用了宏来定义传感器和电机引脚。

代码最初将引脚设置为输入模式或输出模式。接下来,我们存储传感器的输入值。最后,根据传感器输入,我们处理机器人的运动。

在你的 Intel Edison 上烧录代码后,机器人应该会运行。最初尝试一个简单的轨道,一旦你的机器人开始运行,然后尝试更紧密的转弯。再次提醒,要注意在前面代码中,我们的右传感器可能是你的左传感器。在这种情况下,你必须更改位置或只是更改条件。

因此,通过传感器和非常简单的处理相结合,我们可以控制机器人的电机并跟随线条。现在,如果问题陈述要求你反转条件,在黑色表面上跟随白色线条,我们需要稍微修改代码,特别是在条件检查方面。结果将如下:

void loop()  
{ 
int a,b; 
  a=digitalRead(S1); 
  b=digitalRead(S2); 
  //Left turn condition 
if(a==0&&b==1) 
  { 
left(); 
  } 
  //Right turn condition 
else if(a==1&&b==0) 
  { 
right(); 
  } 
  //Stop condition 
else if(a==0&&b==0) 
  { 
stop(); 
  } 
  //By default forward 
else 
  { 
forward(); 
  } 
} 

只需交换 1 和 0。

现在我们对开发基本循线机器人有了相当基本的了解,让我们简要地看看循线的高级形式,并解决一些基本概念。

高级循线机器人概念

到目前为止,我们一直专注于跟随单条线的简单循线机器人。现在让我们稍微复杂一点,尝试使用之前的逻辑解决轨道的以下部分:

交叉轨道

在前面的轨道上,如果我们使用两个传感器,那么事情就会变得失控,因为机器人应该向前移动,但根据之前讨论的算法,当两个传感器都返回1时,机器人应该停止。那么我们如何处理这种情况?

答案在于使用超过两个传感器。让我们看看以下图示:

交叉 3 传感器概念

在前面的图中,我们展示了三个传感器的使用。让我们考虑以下值:

  • 如果传感器位于黑色区域,它发送1

  • 如果传感器位于白色区域,它发送0

现在,在位置 1,我们得到一个值为010,而在位置 1,值是111。这意味着111代表一个交叉点。我们也可以为左右交汇处使用它:

交叉 - 2

在这里,左侧(位置 1)的值为110,而右侧的值为011。现在更容易检测交点,同时也能防止机器人执行错误的转向。要在代码中实现这一点,非常简单:

void loop()  
{ 
int a,b,c; 
  a=digitalRead(S1); 
  b=digitalRead(S2); 
  c=digitalRead(S3); 
  //Left turn condition 
if(a==1&&b==1&&c==0) 
  { 
left(); 
  } 
  //Right turn condition 
else if(a==0&&b==1&&c==1) 
  { 
right(); 
  } 
  //Stop condition 
else if(a==0&&b==0&&c==0) 
  { 
stop(); 
  } 
  //By default forward 
else 
  { 
forward(); 
  } 
} 

应该注意的是,前面的方法需要根据轨道的情景应用。有时甚至需要忽略交叉点。这完全取决于轨道。传感器的放置也在循线中扮演着非常关键的角色。

有一种流行的循线机器人,其传感器排列呈曲线状。它是 Polulu 3pi 机器人:

Polulu 3pi 机器人。图片来源:www.pololu.com/product/975

通常我们为循线机器人使用五个或六个传感器。它作为一个名为线传感器阵列的模块:

线传感器阵列。图片来源:robokits.co.in/sensors/line-sensor-array

比例积分微分(PID)控制

比例积分微分PID)是一种控制回路反馈机制。使用基于 PID 的控制的主要目的是高效地控制电机或执行器。PID 的主要任务是使我们所控制的任何东西的误差最小化。

它接收输入并计算与预期行为的偏差或误差,最终调整输出。

在较低速度下,跟随线条可能很准确,但在较高速度下,事情可能会失控。这时 PID 就派上用场了。让我们看看一些术语:

  • 误差:误差是设备没有正确执行的事情。如果电机的 RPM 是 380,而期望的 RPM 是 372,那么误差是 380-372=8。

  • 比例P):它与误差成正比。

  • 积分I):它取决于一段时间内的累积误差。

  • 导数D):它取决于误差的变化率。

  • 常数因子:当 P、I 和 D 项包含在代码中时,是通过乘以某些常数因子来完成的:

    • P: 因子(Kp)

    • I: 因子(Ki)

    • D: 因子(Kd)

  • 误差测量:考虑一个具有五个传感器阵列的跟随线条机器人,该阵列返回数字值;让我们看看误差测量。从传感器获得的输入需要根据输入的可能组合进行加权。考虑以下表格:

**                    二进制值** 加权值
00001 4
00011 3
00010 2
00110 1
00100 0
01100 -1
01000 -2
11000 -3
10000 -4
00000 -5

值的范围是从 -5 到 +5。机器人的位置测量每秒进行几次,然后,通过平均值,我们确定误差。

  • PID 公式:计算出的误差值需要影响机器人的实际运动。我们需要简单地将误差值添加到输出中,以调整机器人的运动。

  • 比例

差分 = 目标位置 - 当前位置

比例 = Kp * 差分

前面的方法有效,但发现对于快速响应时间,如果我们使用大的常数或误差值很大,则输出会超过设定点。为了避免这种情况,导数被引入到图中。

  • 导数:导数是误差变化的速率。

变化率 = (差分 - 上一次差分)/ 时间间隔

导数 = Kd * 变化率

通过使用英特尔爱迪生的定时器控制来获取时间间隔。这有助于我们计算误差变化的快慢,并根据这一点设置输出。

  • 积分

积分 = 积分 + 差分

积分 = Ki * 积分

积分改善了稳态性能。因此,所有误差都被累加起来,并将结果应用于机器人的运动。

最终的控制信号来自这里:

比例 + 积分 + 微分

  • 调整:PID 实现不能帮助你,除非它被调整,否则它将降低机器人的运动。调整参数是 Kp、Ki 和 Kd。调整值取决于各种参数,例如地面的摩擦、光照条件、质心等。它因机器人而异。

将所有设置都设为零,并首先设置 Kp。将 Kp 设为1并观察机器人的状态。目标是使机器人即使在摇摆的情况下也能跟随线条。如果机器人超出了界限并失去了线条,则减小 Kp。如果它无法转弯或看起来反应迟钝,则增加 Kp。

一旦机器人大致正确地跟随线条,将1分配给 Kd。现在跳过 Ki。增加 Ki 的值,直到你看到更少的摇摆。一旦机器人相当稳定并且能够大致正确地跟随线条,将 0.5-1 之间的值分配给 Ki。如果值太低,就不会发现太大的差异。如果值太高,那么机器人可能会快速左右摆动。你可能需要以.01的增量进行增加。

PID 如果不经过适当的调整,就不会产生有效结果,所以仅仅编码是无法得到正确结果的。

对读者的开放式问题

已经解释了 PID 用例。尝试在代码中实现它,并使用五到六个传感器编写和实现一个线跟踪算法。这些实践用例将解释线跟踪背后的所有概念。

摘要

在本章关于自主机器人的章节中,我们涵盖了多个主题,包括处理传感器和电机驱动器,以及如何校准传感器和测试电机驱动器。我们还详细介绍了线跟踪机器人的用例,并有机会了解更高级的控制方法,最终以基于 PID 的控制系统结束。

在第六章《使用英特尔爱迪生的手动机器人》中,我们将涵盖手动机器人并开发一些控制器软件。我们还将涵盖更多与机器人相关的硬件主题。

第六章:使用英特尔爱迪生的手动机器人

在第五章《使用英特尔爱迪生的自主机器人》中,我们处理了机器人和其自主方面。在这里,我们将深入探讨手动机器人的领域。一个手动机器人可能通常不会被称作机器人,所以更具体地说,我们将处理具有一些自主特性的机器人的手动控制。我们主要处理 UGVs 的开发及其使用 WPF 应用程序的控制。WPF 应用程序已经在第三章《英特尔爱迪生和物联网(家庭自动化)》中讨论过,在那里我们使用 MQTT 协议与爱迪生通信。在这里,我们将使用串行端口通信做同样的事情。我们还将学习如何使我们的机器人完全无线化。我们将讨论的主题如下:

  • 手动机器人系统——架构和概述

  • 两轮驱动和四轮驱动机制

  • 使用英特尔爱迪生的串行端口通信

  • 使机器人无线化

  • 一个简单的 WPF 应用程序,使用英特尔爱迪生开关 LED 的开关

  • 带代码的高性能电机驱动器示例

  • 黑色:地面无人车(UGV)的 e-track 平台

  • UGV 的通用机器人控制器

本章的所有代码都将使用 Arduino IDE 编写,而在 Visual Studio 的软件方面,我们使用 C#和 xaml。

手动机器人系统

我们已经研究了自主机器人架构。手动机器人也处理类似的架构;唯一的区别是我们有一个完整的控制器,负责大部分动作:

图片

手动机器人架构

这里讨论的架构与第五章中讨论的架构没有太大区别,即《使用英特尔爱迪生的自主机器人》。我们在这里增加了一个接收器和发射器单元,这在早期的用例中也是存在的。在处理机器人时,整个架构都处于同一屋檐下。

手动机器人可能不仅限于只有手动机器人。它可能是手动和自主功能的结合,因为一个完全手动的机器人通常不会被称作机器人。然而,我们了解地面无人车UGVs)和无人机UAVs)。有时术语可能将它们定义为机器人,但除非它们至少有一些手动功能,否则可能不会被称作机器人。本章主要涉及 UGVs,就像每个机器人或 UGV 一样,我们需要一个坚固的车架。

机器人车架:两轮驱动和四轮驱动

预期读者将开发自己的机器人,因此你将需要了解驱动机制和底盘的选择。理想情况下,有两种类型的驱动机制,底盘的选择基于所使用的驱动机制。通常我们不希望底盘过度应力我们的电机,也不希望它在户外环境中可能会卡住。在典型的循线机器人中,如第五章自主机器人与英特尔爱迪生中讨论的,最常见且最广泛使用的驱动机制是两轮驱动,因为这些通常在平滑表面和室内环境中运行。

两轮驱动

两轮驱动(2WD)指的是涉及两个电机和两个轮子的驱动机制,它可能通常包含一个万向轮以实现平衡:

图片

2WD 典型布局

后置电机提供移动性,并作为机器人的转向机制。为了使机器人向右移动,你可以关闭右侧电机,让左侧电机工作。然而,这样做的转弯半径可能会更极端,左侧电机的功耗会增加,因为它需要克服右侧轮子提供的摩擦力。万向轮提供的阻力较小,但这并不一定是首选的。另一种方法是让右侧轮子向后旋转,而左侧轮子向前移动;这种方法允许机器人在其轴上旋转,并提供零转弯半径:

图片

2WD 机器人在其轴上的转弯

当我们遵循前面的方法时,对电机的压力会小得多,由于万向轮是全方位的,机器人执行几乎完美的转弯。

底盘可以用任何材料制成,设计应该是这样的,它对电机施加的压力尽可能小。

然而,当处理四轮驱动时,设计起着作用:

图片

4WD 典型驱动

通常,这些由四个电机(在这里,电机 1 是左侧电机,而电机 2 是右侧电机)提供动力,可以独立控制。通常在旋转时,我们不会停止另一侧的电机,因为这会给这些电机造成很大的压力。另一个可能的选择是在相对的侧面旋转——但是有一个陷阱。

通常情况下,在这些情况下,机器人的长度需要等于宽度,甚至更短。否则,可能会出现一种称为打滑的条件。为了防止这种情况,设计通常是整个模型及其轮子都适合在一个圆圈内,如下所示:

图片

4WD—设计

另一个可能考虑的参数是两个轮子之间的距离,因为它必须小于轮子的直径。这是因为如果我们遇到崎岖地形,机器人将能够出来。

如果机器人的结构适合圆形,并且前后轮之间的长度和距离小于左右两侧的距离,这种情况就会发生。在这里,虽然会发生打滑,但打滑程度大大降低,几乎可以忽略不计。查看以下图片以获取更多信息:

4WD 机器人的旋转

在前面的图像中,随着机器人倾向于保持在它所包围的圆形中,执行更多或更少的枢轴转向或零半径转向,这一概念应该会变得更加清晰。

既然我们已经知道了如何设计机器人底盘,让我们看看可用的现成设计。在亚马逊和 eBay 等网站上,有许多底盘是预制好的,遵循现有的设计模式。如果你想要自己制造底盘,那么最好遵循前面的设计模式,尤其是在 4WD 配置中。

与 Intel Edison 的串口通信

当我们有一个手动机器人时,我们需要控制它。因此,为了控制它,我们需要某种通信方式。这是通过使用串口通信来实现的。在 Intel Edison 中,我们有三个串口;让我们称它为 Serialx,其中 x 代表 1 或 2。这些串口可以通过 Arduino IDE 访问:

  • Serial:

    • Name: 多功能,固件编程,串行控制台或 OTG 端口

    • Location: Arduino 扩展板中心附近的 USB 微型连接器

    • ArduinoSWname: Serial

    • Linuxname: /dev/ttyGS0

这个端口允许我们编程 Intel Edison,也是 Arduino IDE 的默认端口。在 Arduino 扩展板中,当切换开关或 SW1 指向 OTG 端口且远离 USB 插槽时,此端口被激活。

  • Serial1:

    • Name: UART1,通用 TTL 级端口(Arduino 屏蔽兼容性)

    • Location: Arduino 屏蔽接口引脚上的 0(RX)和 1(TX)。

    • ArduinoSWname: Serial1

    • Linuxname: /dev/ttyMFD1

这个端口是 0 号和 1 号引脚,用作 Rx 和 Tx。这个端口用于通过 RF 网络或任何外部蓝牙设备远程控制 Edison。

  • Serial2:

    • Name: UART2,Linux 内核调试或调试喷口

    • Location: Arduino 板边缘附近的 USB 微型连接器

    • ArduinoSWname: Serial2

    • Linuxname: /dev/ttyMFD2

这是其中一个最有用的端口,其通信波特率为 115200。这通常是可以通过 PuTTY 控制台访问的端口,用于隔离启动问题。当创建并使用Serial2.begin()初始化 Serial2 对象时,内核对端口的访问被移除,直到调用Serial2.end(),Arduino 草图将获得对端口的控制。

  • Virtual ports:

    • Name: VCP 或虚拟通信端口(仅在串行 USB 设备连接时出现)

    • Location: 最靠近 Arduino 电源连接器的较大类型 A USB 端口

    • ArduinoSWname: 默认不支持

    • Linuxname: /dev/ttyACMx/dev/ttyUSBx

这是英特尔爱迪生的 Arduino 扩展板的 USB 端口。开关必须朝向 USB 端口以启用设备。可以使用 USB 集线器连接多个 USB 设备。

考虑以下代码示例:

void setup()
  {
    Serial.begin(9600);
  }

void loop()
  {
    Serial.println("Hi, Reporting in from Intel Edison");
    delay(500);
  }

这将在串行监视器中打印“Hi,来自英特尔爱迪生的报告”。从代码中可以看出,使用了Serial,这是默认的。

使系统无线化

在机器人技术中使系统无线化,有许多可用的选项。硬件和协议的选择取决于某些因素,如下所述:

  • 移动网络覆盖的可用性

  • 在您所在运营国家的射频(RF)规则和规定

  • 需要的最大距离

  • 互联网连接的可用性

如果我们使用 GSM 模块,那么移动网络覆盖是必须的。我们可能需要获得射频的许可,并确保它不会干扰其他信号。最大距离是另一个需要考虑的因素,因为在使用蓝牙时距离是有限的。如果距离超过,蓝牙连接可能会受到影响。对于射频来说也是一样,但基于使用的天线,射频覆盖可以增加。如果某个区域有互联网连接,那么可以使用 MQTT 本身,这已经在第三章中讨论过,英特尔爱迪生和物联网(家庭自动化)

射频(RF)可用于小型应用。爱迪生也可以使用 Wi-Fi,但让我们覆盖广泛的设备,并看看射频如何被使用。

通常,射频模块遵循通用异步接收发送器UART)协议。这些通常具有 USB 链路和串行链路。可以使用串行到 USB 转换器将串行链路转换为 USB 链路。购买射频模块套件时有很多选择。

记录最大范围和操作频率。所有详细信息都可以从购买产品的地点获得。

通常,射频串行链路的引脚图如下所示:

图片

射频串行链路引脚图

这里是我们项目中使用的产品robokits.co.in/

图片

射频 USB 串行链路。图片来源:robokits.co.in/

该模块可以由五个引脚组成。我们只需要处理前面提到的四个引脚。

使用射频套件可以通过发送命令手动控制机器人无线。这些命令是通过串行端口通信发送的。控制器可能使用具有 USB 链路的射频模块,或者您可以使用串行到 USB 转换器将其连接到您的 PC。射频串行链路与串行到 USB 转换器的连接如下所示:

图片

将射频串行链路连接到串行转 USB 转换器的连接

之前显示的连接用于将 RF 串行链路连接到 USB。这适用于计算机端,因为我们想通过 PC 来控制它。我们必须使用两个 RF 模块;一个用于爱迪生,另一个用于控制器应用程序或 PC。要将 RF 模块连接到英特尔爱迪生,请查看以下图像:

RF 串行链路连接到英特尔爱迪生的连接

英特尔爱迪生具有 Rx 和 Tx 引脚,分别对应引脚 0 和 1。整体架构如下所示:

无线控制英特尔爱迪生

现在我们知道了硬件组件如何用于无线通信,英特尔爱迪生中先前模型的编程部分非常简单。只需将Serial替换为Serial1,因为我们正在使用 Rx 和 Tx 引脚:

void setup()  
  { 
    Serial1.begin(9600); 
  } 

void loop()  
  { 
    Serial1.println("Hi, Reporting in from Intel Edison"); 
    delay(500); 
  } 

上述代码通过 RF 网络使用 Rx 和 Tx 引脚将数据发送到控制器应用程序。现在我们将查看控制器应用程序端,我们将开发一个 WPF 应用程序来控制我们的设备。

LED 开关的 WPF 应用程序

在第三章中,我们探讨了使用 WPF 应用程序和 MQTT 连接,了解到我们可以使用 MQTT 协议来控制我们的英特尔爱迪生。然而,在这里,我们将处理串行端口通信。由于我们已经讨论了 WPF 应用程序以及如何创建项目,并创建了一个 hello world 应用程序,因此我们不会在本章中讨论基础知识,而是直接进入应用程序。本章的问题陈述是通过串行端口通信使用 WPF 应用程序开关 LED。

从创建一个新的 WPF 项目并命名为RobotController开始:

机器人控制器—1

接下来,在 MainWindow.xaml 中,我们将设计 UI。我们将使用以下控件:

  • Buttons

  • TextBox

  • TextBlocks

按照以下方式设计您的 UI:

机器人控制器—2

上述 UI 的 xaml 代码如下:

<Window x:Class="RobotController.MainWindow"        mc:Ignorable="d" Title="MainWindow" Height="350" Width="525" Background="#FF070B64 <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="335*"/> <ColumnDefinition Width="182*"/> </Grid.ColumnDefinitions> <TextBlock x:Name="status" HorizontalAlignment="Left"
    Margin="172,111,0,0" TextWrapping="Wrap" Text="Disconnected"
    VerticalAlignment="Top" Foreground="White"/> <TextBox x:Name="comno" HorizontalAlignment="Left" Height="23"
    Margin="172,83,0,0" TextWrapping="Wrap" Text="COM13"
    VerticalAlignment="Top" Width="177" Grid.ColumnSpan="2"/> <Button x:Name="on" Content="ON" HorizontalAlignment="Left"
    Margin="170,191,0,0" VerticalAlignment="Top" Width="74" Height="52"
    Click="on_Click"/> <Button x:Name="off" Content="OFF" HorizontalAlignment="Left"
    Margin="275,191,0,0" VerticalAlignment="Top" Width="74" Height="52"
    Grid.ColumnSpan="2" Click="off_Click"/> <Button x:Name="connect" Content="Connect"
    HorizontalAlignment="Left" Margin="172,132,0,0"
    VerticalAlignment="Top" Width="75" Click="connect_Click"/> <Button x:Name="disconnect" Content="Disconnect"
    HorizontalAlignment="Left" Margin="274,132,0,0"
    VerticalAlignment="Top" Width="75" Grid.ColumnSpan="2"
    Click="disconnect_Click"/> </Grid> </Window>

默认情况下,我们已编写COM13;然而,这可能会改变。总共添加了四个按钮,分别是开、关、连接和断开连接。我们还有一个TextBlock来显示状态。您可以修改此代码以进行更多定制。

现在我们需要编写此代码的后端,这还将包括其背后的逻辑。

让我们先创建事件处理程序。双击每个按钮以创建一个事件。前面的代码包含事件处理程序。完成后,包含以下命名空间以使用SerialPort类:

using System.IO.Ports;  

接下来,创建SerialPort类的对象:

SerialPort sp= new SerialPort(); 

现在导航到连接按钮的事件处理程序方法,并在此处添加通过串行端口将您的应用程序连接到英特尔爱迪生的代码。添加了 try catch 块以防止连接时崩溃。崩溃的最常见原因是端口号码不正确或 USB 未连接:

try 
  { 
    String portName = comno.Text; 
      sp.PortName = portName; 
      sp.BaudRate = 9600; 
      sp.Open(); 
      status.Text = "Connected"; 
  } 
    catch (Exception) 
  { 
     MessageBox.Show("Please give a valid port number or check your connection");    
  } 

在前面的代码中,我们将com端口号存储在一个字符串类型的变量中。接下来,我们将对象的PortName成员赋值为portName。我们还设置了波特率为9600。最后,我们打开端口并在状态框中写入connected

接下来,我们编写断开连接的事件处理程序代码:

try 
  { 
    sp.Close(); 
    status.Text = "Disconnected"; 
  } 
    catch (Exception) 
  { 
    MessageBox.Show("First Connect and then disconnect"); 
  } 

sp.close()断开连接。在 try catch 块下编写这些代码是安全的。

最后,我们编写了开和关按钮的事件处理程序代码:

private void on_Click(object sender, RoutedEventArgs e) 
  { 
    try 
      { 
        sp.WriteLine("1"); 
      } 
        catch(Exception) 
      { 
         MessageBox.Show("Not connected"); 
      } 
  } 

private void off_Click(object sender, RoutedEventArgs e) 
  { 
    try 
      { 
        sp.WriteLine("2"); 
      } 
        catch (Exception) 
      { 
        MessageBox.Show("Not connected"); 
      } 
  } 

在前面的代码中,我们使用了WriteLine方法并发送了一个字符串。与使用串行端口连接到应用程序的设备接收到的字符串可能触发一个动作。这总结了整个过程。MainWindow.xaml.cs的整个代码如下所示:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Data; 
using System.Windows.Documents; 
using System.Windows.Input; 
using System.Windows.Media; 
using System.Windows.Media.Imaging; 
using System.Windows.Navigation; 
using System.Windows.Shapes; 
using System.IO.Ports; 

namespace RobotController 
{ 
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    public partial class MainWindow : Window 
    { 
        SerialPort sp = new SerialPort(); 
        public MainWindow() 
        { 
            InitializeComponent(); 
        } 

        private void connect_Click(object sender, RoutedEventArgs e) 
        { 
            try 
            { 
                String portName = comno.Text; 
                sp.PortName = portName; 
                sp.BaudRate = 9600; 
                sp.Open(); 
                status.Text = "Connected"; 
            } 
            catch (Exception) 
            { 

                MessageBox.Show("Please give a valid port number or check your connection"); 
            } 

        } 

        private void disconnect_Click(object sender, RoutedEventArgs e) 
        { 
            try 
            { 
                sp.Close(); 
                status.Text = "Disconnected"; 
            } 
            catch (Exception) 
            { 

                MessageBox.Show("First Connect and then disconnect"); 
            } 
        } 

        private void on_Click(object sender, RoutedEventArgs e) 
        { 
            try 
            { 
                sp.WriteLine("1"); 
            } 
            catch(Exception) 
            { 
                MessageBox.Show("Not connected"); 
            } 
        } 

        private void off_Click(object sender, RoutedEventArgs e) 
        { 
            try 
            { 
                sp.WriteLine("2"); 
            } 
            catch (Exception) 
            { 
                MessageBox.Show("Not connected"); 
            } 
        } 
    } 
} 

现在我们已经准备好了控制英特尔爱迪生的应用程序。让我们来测试一下。打开 Arduino IDE。我们将为英特尔爱迪生编写一小段代码,该代码将从应用程序读取串行数据,以便根据传入的数据使板载 LED 灯亮和灭。

编写以下代码以执行相同操作:

void setup()  
  { 
    pinMode(13,OUTPUT); 
    Serial.begin(9600); 
  } 
void loop()  
  { 
    if(Serial.available()>0) 
      { 
         char c= Serial.read(); 
         if(c=='1') 
         digitalWrite(13,HIGH); 
         else if(c=='2') 
         digitalWrite(13,LOW); 
       } 
  } 

当你烧录此代码时,转到 Visual Studio 并运行你的 WPF 应用程序。输入端口号;它必须与你的 Arduino 编程端口相同,即串行端口。然后,按下开按钮。板载 LED 应该会发光。当你按下关按钮时,它应该熄灭。因此,我们现在对如何通过 WPF 应用程序使用串行端口通信与爱迪生进行通信有了非常基本的了解。随着章节的进展,我们将看到如何通过键盘控制有效地控制机器人。

带代码的高性能电机驱动器示例

在第五章“使用英特尔爱迪生的自主机器人”中,我们看到了 L293D 的应用,并为它编写了一些代码来控制电机。然而,L293D 在高性能应用中失败了。为了解决这个问题,我们简要讨论了替代的高功率驱动器。

在这里,我们将深入探讨驱动器,因为它一直是我的最爱,并且几乎在我们的所有机器人中都得到了应用:

图片

双电机驱动高功率。图片来源:robokits.co.in/motor-drives/dual-dc-motor-driver-20a

驱动器有以下五个控制引脚:

  • Gnd:地

  • DIR:当低电平时,电机朝一个方向旋转;当高电平时,朝另一个方向旋转

  • PWM:脉冲宽度调制以控制电机速度;推荐频率范围是 20Hz - 400Hz

  • BRK:当处于高电平时,它将停止电机运行

  • 5V:电机驱动板输出的调节 5V

现在,让我们编写一段简单的代码来操作这个驱动器及其所有电路:

图片

电机驱动电路图

上述电路非常简单易懂。您不需要连接 5V 引脚。您可以通过短路板上的两个地线来使用单个地线。现在让我们编写一段代码来操作这个驱动器。这个电机驱动器在控制高扭矩电机方面非常高效。由于使用了 PWM 功能,因此我们将使用原始速度122的一半:

int pwm1=6,dir1=2,brk1=3,pwm2=9,dir2=4,brk2=5; 
void setup()  
{ 
  pinMode(2,OUTPUT); 
  pinMode(6,OUTPUT); 
  pinMode(3,OUTPUT); 
  pinMode(4,OUTPUT); 
  pinMode(5,OUTPUT); 
  pinMode(9,OUTPUT); 
} 
void loop()  
{ 
  forward(); 
  delay(10000); 
  backward(); 
  delay(10000); 
  left(); 
  delay(10000); 
  right(); 
  delay(10000); 
  stop(); 
  delay(10000); 
} 
void forward() 
{ 
  //Left side motors  
  digitalWrite(2,LOW); //Direction 
  digitalWrite(3,LOW); //Brake 
  analogWrite(6,122); 
  //Right side motor 
  digitalWrite(4,LOW); //Direction 
  digitalWrite(5,LOW); //Brake 
  analogWrite(9,122); 
} 
void backward() 
{ 
  //Left side motors  
  digitalWrite(2,HIGH);//Direction 
  digitalWrite(3,LOW); //Brake 
  analogWrite(6,122); 
  //Right side motor 
  digitalWrite(4,HIGH);//Direction 
  digitalWrite(5,LOW); //Brake 
  analogWrite(9,122); 
} 
void left() 
{ 
  //Left side motors  
  digitalWrite(2,LOW);//Direction 
  digitalWrite(3,LOW); //Brake 
  analogWrite(6,122); 
  //Right side motor 
  digitalWrite(4,HIGH);//Direction 
  digitalWrite(5,LOW); //Brake 
  analogWrite(9,122); 
} 
void right() 
{ 
  //Left side motors  
  digitalWrite(2,HIGH);//Direction 
  digitalWrite(3,LOW); //Brake 
  analogWrite(6,122); 
  //Right side motor 
  digitalWrite(4,LOW);//Direction 
  digitalWrite(5,LOW); //Brake 
  analogWrite(9,122); 
} 
void stop() 
{ 
  //Left side motors  
  digitalWrite(2,LOW);//Direction 
  digitalWrite(3,HIGH); //Brake 
  analogWrite(6,122); 
  //Right side motor 
  digitalWrite(4,LOW);//Direction 
  digitalWrite(5,HIGH); //Brake 
  analogWrite(9,122); 
} 

在前面的代码中,值得注意的是Brakepwm的功能。即使您使用的是低扭矩电机,如果刹车设置为高,电机也不会旋转。同样,通过pwm引脚可以实现高效的速度控制。因此,我们默认将pwm上的其他所有设置都设置为低。这再次取决于您电机的极性。请随意调整连接,以确保所有设置都符合前面的代码。如果您在正向条件下发现两侧都出现相反的旋转,请反转电机连接。

观察电机是如何通过一个非常简单的代码进行高效控制的。

现在我们知道了如何更有效地控制电机,我们将继续介绍我们特殊的黑色轨道 UGV 平台,我们在该平台上开发了用于控制机器人的控制器。几乎所有部件都是从robokits.co.in购买的。

4WD UGV(黑色轨道)

名称可能有点花哨,但这个 UGV 相当简单,唯一的区别是它包含由单个双驱动器电机驱动电路供电的四个高扭矩电机。最初,我们使用了两个驱动电路,但后来我们转向使用一个。它由锂离子电池供电,但所有测试都是使用 SMPS 进行的。UGV 由名为通用遥控器的 WPF 应用程序控制。这个 UGV 还配备了一个工作频率为 5.8 GHz 的摄像头。UGV 还使用 2.4 GHz 射频模块进行无线连接。让我们看看除了 Intel Edison 之外所需的硬件:

  • 30 cm x 30 cm 底盘(1)

  • 10 cm 直径的轮子(4)

  • 高扭矩电机 300 RPM 12V(4)

  • 20A 双电机驱动器(1)

  • RF 2.4 GHz USB 链路(1)

  • RF 2.4 GHz 串行链路(1)

  • 锂离子电池(最小电压:12V;最大电流:3-4A)

本节将涵盖其硬件方面以及如何使用 WPF 开发控制器应用程序。底盘与车轮的组合属于前述图中讨论的设计原则。让我们看看 UGV 的电路图。如果机器人使用前面提到的硬件制作,那么机器人在崎岖地形上表现良好,并且能够爬上 60-65 度的陡坡(已测试):

UGV 电路图

电机 1 代表左侧电机,而电机 2 代表右侧电机。左侧电机是短路的,同样右侧电机也是如此。这个特定的 UGV 被编程通过串口通信接收某些字符,并根据这些字符执行某些操作。现在,让我们看看 Intel Edison 的代码:

#define SLOW_SPEED 165 
#define MAX_SPEED 255 
int pwm1=6,dir1=2,brk1=3,pwm2=9,dir2=4,brk2=5; 
void setup()  
{ 
Serial1.begin(9600); 
  pinMode(2,OUTPUT); 
  pinMode(6,OUTPUT); 
  pinMode(3,OUTPUT); 
  pinMode(4,OUTPUT); 
  pinMode(5,OUTPUT); 
  pinMode(9,OUTPUT); 
} 
/* 
         * 1: Fast front 
         * 0: Fast back 
         * 3: Fast right 
         * 4: Fast left 
         * 5: STOP 
         * 6: Slow front 
         * 7: Slow back 
         * 8: Slow right 
         * 9: Slow left 
         * */ 
void loop()  
{ 
  if(Serial1.available()>0) 
  { 
    char c= Serial1.read(); 
    if(c=='1') 
    forward(MAX_SPEED); 
    else if(c=='0') 
    backward(MAX_SPEED); 
    else if(c=='3') 
    right(MAX_SPEED); 
    else if(c=='4') 
    left(MAX_SPEED); 
    else if(c=='6') 
    forward(SLOW_SPEED); 
    else if(c=='7') 
    backward(SLOW_SPEED); 
    else if(c=='8') 
    right(SLOW_SPEED); 
    else if(c=='9') 
    left(SLOW_SPEED); 
    else if(c=='5') 
    stop();   
  } 
} 
void forward(int speed) 
{ 
  //Left side motors  
  digitalWrite(2,LOW); //Direction 
  digitalWrite(3,LOW); //Brake 
  analogWrite(6,speed); 
  //Right side motor 
  digitalWrite(4,LOW); //Direction 
  digitalWrite(5,LOW); //Brake 
  analogWrite(9,speed); 
} 
void backward(int speed) 
{ 
  //Left side motors  
  digitalWrite(2,HIGH);//Direction 
  digitalWrite(3,LOW); //Brake 
  analogWrite(6,speed); 
  //Right side motor 
  digitalWrite(4,HIGH);//Direction 
  digitalWrite(5,LOW); //Brake 
  analogWrite(9,speed); 
} 
void left(int speed) 
{ 
  //Left side motors  
  digitalWrite(2,LOW);//Direction 
  digitalWrite(3,LOW); //Brake 
  analogWrite(6,speed); 
  //Right side motor 
  digitalWrite(4,HIGH);//Direction 
  digitalWrite(5,LOW); //Brake 
  analogWrite(9,speed); 
} 
void right(int speed) 
{ 
  //Left side motors  
  digitalWrite(2,HIGH);//Direction 
  digitalWrite(3,LOW); //Brake 
  analogWrite(6,speed); 
  //Right side motor 
  digitalWrite(4,LOW);//Direction 
  digitalWrite(5,LOW); //Brake 
  analogWrite(9,speed); 
} 
void stop() 
{ 
  //Left side motors  
  digitalWrite(2,LOW);//Direction 
  digitalWrite(3,HIGH); //Brake 
  analogWrite(6,122); 
  //Right side motor 
  digitalWrite(4,LOW);//Direction 
  digitalWrite(5,HIGH); //Brake 
  analogWrite(9,122); 
} 

之前的代码根据接收到的数据执行函数。以下表格总结了负责接收数据的字符:

接收到的字符 执行的操作
0 快速后退
1 快速向前
3 快速向右
4 快速向左
5 停止
6 慢速向前
7 慢速后退
8 慢速向右
9 慢速向左

我们创建了两个宏,用于最大速度和慢速。运动执行方法参数是基于接收到的数据传递的速度。您可以使用串行监视器进行测试。现在,我们有了硬件,让我们为它编写软件。这个软件将能够通过键盘控制机器人。

UGV 的通用机器人控制器

在深入探讨控制器之前,请将以下 GitHub 仓库克隆到您的电脑上。代码本身大约有 350+行,因此需要讨论的部分:

github.com/avirup171/bet_controller_urc

因此,让我们首先设计 UI:

URC 截图

为了简化,包括了快速和慢速控制的两部分。然而,它们可以被合并为一个,并使用复选框。我们在右上角有一个连接面板。命令在此显示。添加了一个默认密码12345,这是为了避免崩溃和未经授权的使用。然而,这是一个简单的控制器,可以相当高效地与 UGVs 一起使用。

如果您仔细查看 UI,会发现一个名为“按下以激活键盘控制”的按钮。一旦点击该按钮,键盘控制就会被激活。现在您需要分配键盘按下和键盘释放事件。这可以通过选择控件并在属性窗口中点击以下图标来完成。这管理着所选控件的全部事件处理器:

属性窗口

当我们在键盘上按下键时,会触发两个事件。第一个事件是我们按下键时,第二个事件是我们释放键时

现在当您点击它时,您将得到与之相关的所有可能事件。滚动到 KeyDown 和 KeyUp。双击两者以创建相关的事件处理器。控制按钮有不同的事件与之关联。也就是说,当按钮被按下时,我们发送数据。当按钮释放时,发送 5,这是停止的代码。您可以通过属性窗口(如前所述)分配事件:

<Button x:Name="front_fast" Content="Front" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="204,56,0,0" Height="48" GotMouseCapture="front_fast_GotMouseCapture" LostMouseCapture="front_fast_LostMouseCapture" />

为所有按钮分配名称并创建它们各自的事件处理程序。我们还创建了三个进度条。进度条的 xaml 代码如下所示:

<ProgressBar x:Name="pb1" HorizontalAlignment="Left" Height="10" Margin="92,273,0,0" VerticalAlignment="Top" Width="300"/>

对于键盘上下事件,相应的 xaml 代码如下:

<Button Content="Press to activate keyboard controls" Name="kbp" HorizontalAlignment="Left" Margin="582,454,0,0" VerticalAlignment="Top" Width="202" Height="52" KeyDown="kbp_KeyDown" KeyUp="Kbp_KeyUp"/>

创建了两个事件。一个用于按键按下,另一个用于按键释放。

之前 UI 的 xaml 代码如下所示:

<Window x:Class="BET_Controller_v2.MainWindow"        Title="URC V1.0.0.1" Height="720" Width="1360" Loaded="Window_Loaded">
 <Grid> <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap"
    Text="Controls invloving high speed" VerticalAlignment="Top"
    Height="25" Width="269" Margin="10,0,0,0"/> <Rectangle Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="13"
    Stroke="Black" VerticalAlignment="Top" Width="498"
    Margin="10,25,0,0"/> <Button x:Name="front_fast" Content="Front"
      HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"
      Margin="204,56,0,0" Height="48"
      GotMouseCapture="front_fast_GotMouseCapture"
      LostMouseCapture="front_fast_LostMouseCapture" /> <Button x:Name="back_fast" Content="back"
      HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"
      Margin="204,193,0,0" Height="53"
      GotMouseCapture="back_fast_GotMouseCapture"
      LostMouseCapture="back_fast_LostMouseCapture"/>
      <Button x:Name="left_fast" Content="left"
      HorizontalAlignment="Left" VerticalAlignment="Top" Width="74"
      Margin="92,125,0,0" Height="50"
      GotMouseCapture="left_fast_GotMouseCapture"
      LostMouseCapture="left_fast_LostMouseCapture" />
 <Button x:Name="right_fast" Content="right"
      HorizontalAlignment="Left" VerticalAlignment="Top" Width="76"
      Margin="316,125,0,0" Height="50"
      GotMouseCapture="right_fast_GotMouseCapture"
      LostMouseCapture="right_fast_LostMouseCapture" /> <Button x:Name="stop_fast" Content="stop"
      HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"
      Margin="204,125,0,0" Height="50"
      RenderTransformOrigin="0.362,0.5" Click="stop_fast_Click" /> <Rectangle Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="13"
    Stroke="Black" VerticalAlignment="Top" Width="498"
    Margin="10,305,0,0"/> <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap"
  Text="Controls invloving low speed" VerticalAlignment="Top"
  Height="25" Width="269" Margin="10,323,0,0"/> <Button x:Name="front_slow" Content="front"
      HorizontalAlignment="Left" Margin="204,374,0,0"
      VerticalAlignment="Top" Width="75" Height="53"
      GotMouseCapture="front_slow_GotMouseCapture"
      LostMouseCapture="front_slow_LostMouseCapture"/> <Button x:Name="back_slow" Content="back"
      HorizontalAlignment="Left" Margin="204,526,0,0"
      VerticalAlignment="Top" Width="75" Height="53"
      LostMouseCapture="back_slow_LostMouseCapture"
      GotMouseCapture="back_slow_GotMouseCapture" /> <Button x:Name="right_slow" Content="right"
      HorizontalAlignment="Left" Margin="316,454,0,0"
      VerticalAlignment="Top" Width="76" Height="53"
      LostMouseCapture="right_slow_LostMouseCapture"
      GotMouseCapture="right_slow_GotMouseCapture" /> <Button x:Name="left_slow" Content="left"
      HorizontalAlignment="Left" Margin="92,454,0,0"
      VerticalAlignment="Top" Width="74" Height="53"
      GotMouseCapture="left_slow_GotMouseCapture"
      LostMouseCapture="left_slow_LostMouseCapture" /> <Button x:Name="stop_slow" Content="stop"
      HorizontalAlignment="Left" Margin="204,454,0,0"
      VerticalAlignment="Top" Width="75" Height="53"
      Click="stop_slow_Click"/> <Rectangle Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="13"
    Stroke="Black" VerticalAlignment="Top" Width="255"
    Margin="552,25,0,0"/> <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap"
  Text="Special controls" VerticalAlignment="Top" Height="25"
  Width="269" Margin="552,0,0,0"/> <Button x:Name="rr360" Content="360 deg rotation right"
      HorizontalAlignment="Left" Margin="607,94,0,0"
      VerticalAlignment="Top" Width="138" Height="53"
      RenderTransformOrigin="0.498,0.524" Click="rr360_Click"/> <Button x:Name="lr360" Content="360 deg rotation left"
      HorizontalAlignment="Left" Margin="607,193,0,0"
      VerticalAlignment="Top" Width="138" Height="53"
      RenderTransformOrigin="0.498,0.524" Click="lr360_Click" /> <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap"
  Text="Command will be displayed below" VerticalAlignment="Top"
  Height="25" Width="484" Margin="858,13,0,0"/> <Rectangle Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="10"
    Stroke="Black" VerticalAlignment="Top" Width="484"
    Margin="858,43,0,0"/> <TextBlock x:Name="cmd" HorizontalAlignment="Left"
  Margin="858,72,0,0" TextWrapping="Wrap" Text="Commands"
  VerticalAlignment="Top" Height="174" Width="484"/> <TextBox x:Name="cno" HorizontalAlignment="Left" Height="29"
  Margin="858,273,0,0" TextWrapping="Wrap" Text="COM13"
  VerticalAlignment="Top" Width="85"/> <Button x:Name="connect" Content="Connect"
      HorizontalAlignment="Left" Margin="858,307,0,0"
      VerticalAlignment="Top" Width="85" Height="41"
      RenderTransformOrigin="0.498,0.524" Click="connect_Click" /> <Button x:Name="disconnect" Content="Disconnect"
      HorizontalAlignment="Left" Margin="964,307,0,0"
      VerticalAlignment="Top" Width="85" Height="41"
      RenderTransformOrigin="0.498,0.524" Click="disconnect_Click" /> <Button x:Name="rst" Content="Reset" HorizontalAlignment="Left"
      Margin="607,295,0,0" VerticalAlignment="Top" Width="138"
      Height="53" RenderTransformOrigin="0.498,0.524" Click="rst_Click"
      /> <TextBlock HorizontalAlignment="Left" Margin="858,353,0,0" x:Name="s" 
  TextWrapping="Wrap" Text="Click Connect to connect"
  VerticalAlignment="Top" Height="33" Width="191"/> <ProgressBar x:Name="pbkc" HorizontalAlignment="Left" Height="10"
    Margin="549,569,0,0" VerticalAlignment="Top" Width="272"/> <ProgressBar x:Name="pb2" HorizontalAlignment="Left" Height="10"
    Margin="92,631,0,0" VerticalAlignment="Top" Width="300"/> <ProgressBar x:Name="pb1" HorizontalAlignment="Left" Height="10"
    Margin="92,273,0,0" VerticalAlignment="Top" Width="300"/> <Rectangle Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="12"
    Stroke="Black" VerticalAlignment="Top" Width="269"
    Margin="552,374,0,0"/> <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap"
  Text="Keybooard controls" VerticalAlignment="Top" Height="25"
  Width="269" Margin="552,392,0,0"/> <Button Content="Press to activate keyboard controls" Name="kbp"
      HorizontalAlignment="Left" Margin="582,454,0,0"
      VerticalAlignment="Top" Width="202" Height="52"
      KeyDown="kbp_KeyDown" KeyUp="Kbp_KeyUp"/> <Rectangle Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="11"
    Stroke="Black" VerticalAlignment="Top" Width="484"
    Margin="858,391,0,0"/> <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Enter
  the password below to activate all controls. The password was
  supplied by the administrator with the bundel."
  VerticalAlignment="Top" Height="51" Width="269"
  Margin="858,414,0,0"/> <Button x:Name="activate" Content="Activate"
      HorizontalAlignment="Left" Margin="1052,631,0,0"
      VerticalAlignment="Top" Width="75" Height="33"
      Click="activate_Click" KeyDown="activate_KeyDown"/> <PasswordBox x:Name="pswrdbox" HorizontalAlignment="Left"
  Margin="1025,585,0,0" VerticalAlignment="Top" Height="25" Width="124"
  PasswordChar="*" FontSize="24"/> <Rectangle Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="645"
    Stroke="Black" VerticalAlignment="Top" Width="5" Margin="834,8,0,0"
    RenderTransformOrigin="0.5,0.5"> </Rectangle><Rectangle Fill="#FFF4F4F5" HorizontalAlignment="Left"
    Height="645" Stroke="Black" VerticalAlignment="Top" Width="5"
    Margin="539,19,0,0" RenderTransformOrigin="0.5,0.5"/>
 </Grid> </Window>

现在 UI 已经准备好,让我们转到主要的 C#代码。事件处理程序也已就位。最初包含System.IO.Ports命名空间并创建该类的一个对象。之后,键盘按下事件将由我们的代码处理:

private void kbp_KeyDown(object sender, KeyEventArgs e) 
        { 
            Keyboard.Focus(kbp); 
            if (e.Key == Key.W) 
            { 
                sp.WriteLine("1"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "W: fast forward"; 
            } 
            else if (e.Key == Key.S) 
            { 
                sp.WriteLine("0"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "S: fast back"; 
            } 
            else if (e.Key == Key.A) 
            { 
                sp.WriteLine("4"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "A: fast left"; 
            } 
            else if (e.Key == Key.D) 
            { 
                sp.WriteLine("3"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "D: fast right"; 
            } 
            else if (e.Key == Key.NumPad8) 
            { 
                sp.WriteLine("6"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "9: slow front"; 
            } 
            else if (e.Key == Key.NumPad2) 
            { 
                sp.WriteLine("7"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "2: slow back"; 
            } 
            else if (e.Key == Key.NumPad6) 
            { 
                sp.WriteLine("8"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "6: slow right"; 
            } 
            else if (e.Key == Key.NumPad4) 
            { 
                sp.WriteLine("9"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "D: slow left"; 
            } 

        } 

在前面的代码中,我们使用了以下键:

序列号 键盘按键 执行命令
1 W 快速前进
2 A 快速左转
3 S 快速后退
4 D 快速右转
5 数字键盘 8 慢速前进
6 数字键盘 2 慢速后退
7 数字键盘 4 慢速左转
8 数字键盘 6 慢速右转

根据输入,我们发送特定的字符。而对于按键抬起或释放事件,我们简单地发送5,表示停止:

private void Kbp_KeyUp(object sender, KeyEventArgs e) 
  { 
    sp.WriteLine("5"); 
    pbkc.IsIndeterminate = false; 
    cmd.Text = "STOP"; 
  } 

连接和断开事件与之前相同。现在每个按钮将有两个方法。第一个是GotMouseCapture,第二个是LostMouseCapture

以快速控制下的前按钮为例:

private void front_fast_GotMouseCapture(object sender, MouseEventArgs e) 
  { 
    sp.WriteLine("1"); 
    cmd.Text = "Fast Forward"; 
    pb1.IsIndeterminate = true; 
 } 

private void front_fast_LostMouseCapture(object sender, MouseEventArgs e) 
  { 
    sp.WriteLine("5"); 
    cmd.Text = "STOP"; 
    pb1.IsIndeterminate = false; 
  } 

类似地,应用于其他控制。只有左右 360 度旋转与按钮点击事件相关联。整个代码如下所示,粘贴自MainWindow.xaml.cs

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Data; 
using System.Windows.Documents; 
using System.Windows.Input; 
using System.Windows.Media; 
using System.Windows.Media.Imaging; 
using System.Windows.Navigation; 
using System.Windows.Shapes; 
using System.IO.Ports; 

namespace BET_Controller_v2 
{ 
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    public partial class MainWindow : Window 
    { 
        SerialPort sp = new SerialPort(); 
        public MainWindow() 
        { 
            InitializeComponent(); 
            Closing += new System.ComponentModel.CancelEventHandler(MainWindow_Closing); 
        } 

        private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) 
        { 
            if (MessageBox.Show("Do you really want to exit?", "Exit", MessageBoxButton.YesNo) == MessageBoxResult.No) 
            { 
                e.Cancel = true; 
            } 
        } 
/* 
         * 1: Fast front 
         * 0: Fast back 
         * 3: Fast right 
         * 4: Fast left 
         * 5: STOP 
         * 6: Slow front 
         * 7: Slow back 
         * 8: Slow right 
         * 9: Slow left 
         * */ 
        //Keyboard Controls 
        private void kbp_KeyDown(object sender, KeyEventArgs e) 
        { 
            Keyboard.Focus(kbp); 
            if (e.Key == Key.W) 
            { 
                sp.WriteLine("1"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "W: fast forward"; 
            } 
            else if (e.Key == Key.S) 
            { 
                sp.WriteLine("0"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "S: fast back"; 
            } 
            else if (e.Key == Key.A) 
            { 
                sp.WriteLine("4"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "A: fast left"; 
            } 
            else if (e.Key == Key.D) 
            { 
                sp.WriteLine("3"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "D: fast right"; 
            } 
            else if (e.Key == Key.NumPad8) 
            { 
                sp.WriteLine("6"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "9: slow front"; 
            } 
            else if (e.Key == Key.NumPad2) 
            { 
                sp.WriteLine("7"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "2: slow back"; 
            } 
            else if (e.Key == Key.NumPad6) 
            { 
                sp.WriteLine("8"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "6: slow right"; 
            } 
            else if (e.Key == Key.NumPad4) 
            { 
                sp.WriteLine("9"); 
                pbkc.IsIndeterminate = true; 
                cmd.Text = "D: slow left"; 
            } 

        } 
//Key release event handlers 
        private void Kbp_KeyUp(object sender, KeyEventArgs e) 
        { 
            sp.WriteLine("5"); 
            pbkc.IsIndeterminate = false; 
            cmd.Text = "STOP"; 
        } 
//Connect to the hardware 
        private void connect_Click(object sender, RoutedEventArgs e) 
        { 
            try 
            { 
                String pno = cno.Text; 
                sp.PortName = pno; 
                sp.BaudRate = 9600; 
                sp.Open(); 
                s.Text = "Connected"; 
            } 
            catch (Exception) 
            { 

                MessageBox.Show("Please check the com port number or the hardware attached to it"); 
            } 
        } 
        //Disconnect from the hardware 
        private void disconnect_Click(object sender, RoutedEventArgs e) 
        { 
            try 
            { 
                sp.Close(); 
                s.Text = "Disconnected"; 
            } 
            catch (Exception) 
            { 

                MessageBox.Show("Some error occured with the connection"); 
            } 
        } 

        private void front_fast_GotMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("1"); 
            cmd.Text = "Fast Forward"; 
            pb1.IsIndeterminate = true; 
        } 

        private void front_fast_LostMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("5"); 
            cmd.Text = "STOP"; 
            pb1.IsIndeterminate = false; 
        } 

        private void back_fast_GotMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("0"); 
            cmd.Text = "Fast Backward"; 
            pb1.IsIndeterminate = true; 
        } 

        private void back_fast_LostMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("5"); 
            cmd.Text = "STOP"; 
            pb1.IsIndeterminate = false; 
        } 

        private void left_fast_GotMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("4"); 
            cmd.Text = "Fast left"; 
            pb1.IsIndeterminate = true; 
        } 

        private void left_fast_LostMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("5"); 
            cmd.Text = "STOP"; 
            pb1.IsIndeterminate = false; 
        } 

        private void activate_Click(object sender, RoutedEventArgs e) 
        { 
         // Password and activation section 
            string s = pswrdbox.Password; 
            if(s=="12345") 
            { 
                MessageBox.Show("Congrats Black e Track Controller V2 is activated"); 
                front_fast.IsEnabled = true; 
                back_fast.IsEnabled = true; 
                stop_fast.IsEnabled = true; 
                left_fast.IsEnabled = true; 
                right_fast.IsEnabled = true; 
                front_slow.IsEnabled = true; 
                back_slow.IsEnabled = true; 
                right_slow.IsEnabled = true; 
                left_slow.IsEnabled = true; 
                stop_slow.IsEnabled = true; 
                rr360.IsEnabled = true; 
                lr360.IsEnabled = true; 
                rst.IsEnabled = true; 
                kbp.IsEnabled = true; 
            } 
            else 
            { 
                MessageBox.Show("Sorry you have entered wrong password. Please enter the correct credentials or contact your system administrator."); 
            } 
        } 

        private void right_fast_GotMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("3"); 
            cmd.Text = "Fast Right"; 
            pb1.IsIndeterminate = true; 
        } 

        private void right_fast_LostMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("5"); 
            cmd.Text = "STOP"; 
            pb1.IsIndeterminate = false; 
        } 

        private void front_slow_GotMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("6"); 
            cmd.Text = "Slow Front"; 
            pb2.IsIndeterminate = true; 
        } 

        private void front_slow_LostMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("5"); 
            cmd.Text = "STOP"; 
            pb2.IsIndeterminate = false; 
        } 
        private void back_slow_LostMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("5"); 
            cmd.Text = "STOP"; 
            pb2.IsIndeterminate = false; 
        } 

        private void back_slow_GotMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("7"); 
            cmd.Text = "Slow Back"; 
            pb2.IsIndeterminate = true; 
        } 
        private void left_slow_GotMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("9"); 
            cmd.Text = "Slow Left"; 
            pb2.IsIndeterminate = true; 
        } 

        private void left_slow_LostMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("5"); 
            cmd.Text = "STOP"; 
            pb2.IsIndeterminate = false; 
        } 

        private void right_slow_LostMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("5"); 
            cmd.Text = "STOP"; 
            pb2.IsIndeterminate = false; 
        } 

        private void right_slow_GotMouseCapture(object sender, MouseEventArgs e) 
        { 
            sp.WriteLine("8"); 
            cmd.Text = "Slow Right"; 
            pb2.IsIndeterminate = true; 
        } 

        private void stop_fast_Click(object sender, RoutedEventArgs e) 
        { 
            sp.WriteLine("5"); 
            cmd.Text = "STOP"; 
        } 

        private void stop_slow_Click(object sender, RoutedEventArgs e) 
        { 
            sp.WriteLine("5"); 
            cmd.Text = "STOP"; 
        } 

        private void rr360_Click(object sender, RoutedEventArgs e) 
        { 
            sp.WriteLine("4"); 
            cmd.Text = "360 deg right rotation"; 
        } 

        private void lr360_Click(object sender, RoutedEventArgs e) 
        { 
            sp.WriteLine("3"); 
            cmd.Text = "360 deg left rotation"; 
        } 

        private void rst_Click(object sender, RoutedEventArgs e) 
        { 
            MessageBox.Show("The control doesn't exist now"); 
        } 

   //Window loaded event handler for deactivating all controls by default 
        private void Window_Loaded(object sender, RoutedEventArgs e) 
        { 
            front_fast.IsEnabled = false; 
            back_fast.IsEnabled = false; 
            stop_fast.IsEnabled = false; 
            left_fast.IsEnabled = false; 
            right_fast.IsEnabled = false; 
            front_slow.IsEnabled = false; 
            back_slow.IsEnabled = false; 
            right_slow.IsEnabled = false; 
            left_slow.IsEnabled = false; 
            stop_slow.IsEnabled = false; 
            rr360.IsEnabled = false; 
            lr360.IsEnabled = false; 
            rst.IsEnabled = false; 
            kbp.IsEnabled = false; 
        } 
        private void activate_KeyDown(object sender, KeyEventArgs e) 
        {             
            if ((e.Key == Key.B) && Keyboard.IsKeyDown(Key.LeftCtrl) && Keyboard.IsKeyDown(Key.LeftAlt)) 
            { 
                MessageBox.Show("Password: 12345"); 
            } 
        }            
    } 
} 

在前面的代码中,需要注意以下事实:

  • 如果未输入密码,所有按钮都将禁用。

  • 密码为12345

  • 所有按钮都与gotMouseCapturelostMouseCapture相关联。

  • 只有 360 度旋转按钮遵循点击事件

一旦您成功开发项目,进行测试。将射频 USB 链路连接到您的 PC。安装所有必需的驱动程序并测试。整个过程如下所述:

  • 将射频 USB 链路连接到您的 PC。

  • 确保您的英特尔爱迪生已开启并连接到我们的机器人。您可以使用 USB 集线器为英特尔爱迪生供电,并将集线器连接到充电宝。

  • 点击连接后,WPF 应用程序应连接到您的射频设备。

  • 测试您的机器人是否工作。使用 5.8 GHz FPV 摄像头从您的 UGV 获取实时视图。

对读者的开放式问题

我们迄今为止开发的是一种无人地面车辆(UGV),而不是典型的机器人,尽管我们可以配置它成为机器人。为了开发一个自主和手动控制的机器人,我们通常设计一个机器人来完成特定的任务,然而我们仍然保留手动控制,以便我们可以在任何时候取回控制权。更恰当地说,它可能既不完全手动也不完全自主。想想无人机。我们只需在地图上指定航点,无人机就会跟随航点。这是经典的例子之一。现在读者的任务是结合之前讨论的线跟踪机器人以及这里讨论的手动机器人,并将它们合并成一个单一的平台。

摘要

我们已经到达了本章以及本书的结尾。在本章中,我们有幸了解了手动机器人和 UGVs 的一些深入概念。我们为机器人控制开发了我们的软件。我们还学习了如何使我们的机器人无线,以及访问多个串行端口的方法。最后,我们使用自己的控制器控制了我们的机器人。在第三章“英特尔爱迪生和物联网(家庭自动化)”,我们学习了如何使用 MQTT 协议通过 Android 应用程序控制爱迪生。这项技术也可以通过使用mraa库来控制机器人。

整本书涵盖了与英特尔爱迪生(Intel Edison)相关的多个主题。现在,你的任务是利用讨论的概念来提出新的项目,并进一步探索。最后两章纯粹集中在基于英特尔爱迪生的机器人技术,但这些概念也可以应用于其他设备,例如 Arduino。访问software.intel.com/en-us/iot/hardware/edison/documentation获取更多详细信息以及更深入的学习关于硬件的内容。

posted @ 2025-10-27 09:11  绝不原创的飞龙  阅读(3)  评论(0)    收藏  举报