65.9K
CodeProject 正在变化。 阅读更多。
Home

IOBOT:构建您自己的 Azure 驱动的机器人

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (27投票s)

2015 年 3 月 30 日

CPOL

43分钟阅读

viewsIcon

88957

downloadIcon

1090

从零开始构建具有语音识别、远程语音控制、基于 Raspberry PI、Intel Galileo 和 Microsoft Azure 的学习智能的机器人。

目录

  1. 引言
  2. 目标
  3. 伺服电机
  4. Raspberry PI 机器人模块
    1. PI 机器人设计
    2. 语音识别
      1. 在 Raspberry PI 上安装 Pocketsphinx
      2. 使用 Pocketsphinx 编程
    3. PI 机器人类
    4. 进行网络调用
    5. 编译代码
  5. Intel Galileo 机器人模块
    1. 在 Intel Galileo Gen 2 上设置 Windows
    2. Galileo 机器人设计
    3. 进行网络调用和 C++ REST SDK
  6. Microsoft Azure
    1. PaaS - Azure SQL 数据库
    2. PaaS - Azure 网站和 ASP.NET MVC Web API
    3. PaaS - Microsoft Azure Mobile Service
    4. IaaS - Windows Server 2012 虚拟机
  7. 待办事项
  8. 花絮
  9. 结论

1. 引言

在我上一篇关于物联网的文章中,我提到“让机器人四处移动而不发生碰撞并不容易”。但是,等等!真的不容易吗?也许是的,也许没那么难。但为什么不尝试自己动手构建一个来了解它是如何工作的呢。本文旨在构建两个可以与人类以及彼此交互的小型机器人。我们将从头开始做几乎所有事情。想象一下《我,机器人》电影中的桑尼角色。不,我们不会做到那种程度。但最高可以达到机器人能够响应我们的命令、学习新命令、与其他机器人通信等,然后您可以根据自己的意愿将其扩展到任何级别。想象力是无限的。

本文的预告视频

 

 

 

本文使用的机器人车身是 Mini Roboactor。但那只是外观和塑料外壳,所有内部组件都是使用 Raspberry PI 和 Intel Galileo 板上的伺服电机、LED、扬声器/麦克风完成的。您可以使用任何方便的玩具机器人车身,不一定是 mini Roboactor 车身,但我喜欢 Roboactor 的外观,所以选择了它。

2. 目标

在本文中,我们将详细讨论各种主题,从伺服电机的工作原理到我们机器人的 Microsoft Azure 服务。让我们假设我们的目标如下:

我们将构建智能机器人并在世界市场上销售(仅假设)。机器人最初除了基本的命令,如举手、睁眼、看左等,什么都不会做。但是,我们将为客户提供一项很棒的功能,让他们可以教授机器人自己的命令。例如,一个人可以教会机器人如何眨眼,另一个人可以教会机器人如何挥手。如果两个相同的命令有不同的动作怎么办?机器人将根据一些规则自行决定选择哪个命令。有趣的部分将是当人们开始教授跳舞动作 + 分配给一个命令的歌曲播放!好的,我将把本文的功能限制在执行新的基本命令上,这意味着您可以教一个由基本命令组成的命令,但您不能教它“如何跳跃”,当然也不能教它“什么是爱”!

本文将采用详细的方法来解释一切,以便您能够制作出具有语音识别和不同 Azure 服务的机器人。总之,以下是我们将在本文中涵盖的重要主题:

  1. 伺服电机 - PWM、PPM:如何自己制作?
  2. Pocketsphinx 语音识别 - JSGF/FSG 格式和多语法。
  3. 用于一个名为“PI”的机器人的 Raspberry PI Linux 模块
  4. 用于另一个名为“Galileo”的机器人的 Intel Galileo Windows 模块
  5. 如何在 Intel Galileo 上使用 Microsoft Windows
  6. 用于远程控制 Galileo 的 Android 模块
  7. 供机器人通信的 Microsoft Azure Platform as a Service (PaaS)
  8. 供机器人通信的 Microsoft Azure Infrastructure as a Service (IaaS)

3. 伺服电机

这不是第一次在文章中解释这个问题了,也有许多库可以处理伺服电机,例如 raspberry pi 的 wiringpi。但如果不知道库的作用就使用它有什么意义呢。所以,您可以使用任何您想控制伺服电机的库,但让我们尝试了解伺服电机是如何工作的。

伺服电机根据从控制电路接收到的脉冲工作。通常,伺服电机有三根线 - 棕色为 GND,红色为 Vcc,橙色为脉冲信号。本文使用的伺服电机是 TowerPro Micro Servo S90,这足以满足玩具机器人的需求,并且这里提供的信息更具体到此伺服电机类型,但对其他伺服电机来说也基本通用。伺服电机通常可以从 0 度旋转到 180 度,但在伺服电机术语中,它们不是度数,而是 PWM(脉冲宽度调制)。当伺服电机在其橙色线上接收到脉冲(PWM)时,它会改变其位置。什么是脉冲?脉冲就是 OFF、ON、OFF。当伺服电机的橙色线电源从 0v 变为 5v 再变为 0v 时,这称为一个脉冲(方波)。所以,要产生一个脉冲,您可以这样做:

// Pulse
digitalWrite(LOW);
digitalWrite(HIGH);
digitalWrite(LOW);

但是,这个脉冲对伺服电机没有意义!为什么?因为伺服电机需要 PWM,而不仅仅是脉冲。PWM 就是脉冲的宽度/持续时间,即脉冲保持 ON/HIGH 的时间,这就是 PWM。如前所述,PWM 定义了伺服电机的位置,因此它保持 ON/HIGH 的持续时间决定了伺服电机的位置。例如:TowerPro Micro Servo 需要 0.5ms 的脉冲持续时间才能达到 0 度,需要 2.4ms 的脉冲持续时间才能达到 180 度。因此,总共 2.4ms - 0.5ms = 1.9ms (1900µs) 定义了该伺服电机的位置。那么,我们能否像这样重写脉冲代码来获得 PWM 呢?

// Pulse Width Modulation
digitalWrite(LOW);
digitalWrite(HIGH);
usleep(2400);                 // 2.4ms to goto 180 degrees
digitalWrite(LOW);

上面的代码可以让 5v 信号保持 2.4ms,这样伺服电机就可以理解这个 PWM 形式的脉冲了。但是,这仍然不足以使位置正确,因为脉冲持续时间只有 2.4ms,而伺服电机需要从 0 度(或任何其他度数)移动到 180 度。所以,一个单独的脉冲无法实现这一点,我们需要的是一系列称为脉冲序列或 PPM(脉冲位置调制)的脉冲。在开始 PPM 之前,我们需要了解另一个重要的事情,称为“帧”。伺服电机每 20ms 有一个刷新周期来查找 PWM,所以每 20ms 的时间给一个脉冲会被伺服电机的刷新周期识别。任何忽略此刷新周期的脉冲都会被伺服电机忽略。最好在每个 PWM 之间保持 20ms 的间隔。这个 20ms 的刷新周期称为帧或周期,一个帧/周期至少应该有一个有效的 PWM 才能使伺服电机响应。因此,脉冲序列或 PPM 使伺服电机移动到给定位置。如果我们希望伺服电机保持其位置,我们需要继续给伺服电机提供脉冲,否则伺服电机将失去保持,位置可能会被任何外部力改变。通过持续给伺服电机脉冲使其保持位置,我们无法用手手动旋转其位置,其保持位置的最大承受力定义为伺服电机的扭矩。如果我们期望伺服电机能够举起、推动或移动一些物体,扭矩就很重要。

好了,到目前为止,我们已经知道什么是 PWM、PPM、20ms 帧等。关于伺服电机,我们可能还需要了解另一个术语是伺服电机的频率。伺服电机的频率就是您可以提供脉冲的频率来进行旋转。伺服电机的频率计算方式是每秒脉冲数。例如:TowerPro Micro Servo SG90 的频率为 50Hz。我们知道 20ms 是一个帧/周期,至少需要一个脉冲。一秒钟有多少个 20ms?50 x 20ms = 1 秒。所以,该伺服电机每秒可以有 50 个脉冲,即 1 秒除以周期(20ms)= 50Hz 频率。下图显示了一个实现 60 度的脉冲序列 PPM。

嗯,对于人类来说,理解 130 度位置很容易。但是,伺服电机无法理解 130 度,我们需要说 1865µs 的 PWM 来实现 130 度。如何做到呢?有一个简单的计算方法。我们需要找到单个角度位置所需的脉冲宽度。我们知道 1900µs 用于实现完整的 180 度。所以,1900/180 = 10.5µs 用于实现一个单独的角度移动。因此,所需的 PWM 为 500µs + (角度 * (1900/180))。现在我们可以轻松地将伺服电机位置移动到给定的角度值。下面是 PI 机器人模块中 BOTServo.cpp 的代码快照,

int pulsewidth = 500;                           // 500 µs lower pulse range
if (pindata->bAngle) 
      pulsewidth = (int)(pindata->nValue *10.5) + 500; // low range pulse + pulse per angle
else                                            // the given value is not angle
      pulsewidth = 500 + pindata->nValue;       // low range pulse + raw pulse width
pindata->gpioControl->digitalWrite(pindata->nPin, HIGH);   // raise the pulse  -- ON
usleep(pulsewidth);       // pulse width delay
pindata->gpioControl->digitalWrite(pindata->nPin, LOW);    // lower the pulse -- OFF
delay(20 - (pulsewidth / 1000)); // 20 ms refresh cycle of servo - the pulse width time we already used

正如我之前提到的,如果我们想让伺服电机保持其位置,或者如果我们想让伺服电机举起某个物体(例如机器人手举起重物),我们应该连续重复 PWM,否则,根据物体的重量,伺服电机可能会失去其位置。但是,仅仅为了本文的目的,我让 BOTServo 类代码仅将 PPM 保持 1 秒钟,以给伺服电机一些时间移动到新位置。这意味着,如果您使用 BOTServo 类中的代码,伺服电机将移动到新位置,但不会保持它 - 它可以被手动推改变。

代码参考:BOTServo.cpp/BOTServo.h

4. Raspberry PI 机器人模块

4.1 PI 机器人设计

IOBOT 的设计简单明了。困难的部分是如何将这些安装在一个玩具机器人车身中,它实际上并不是为这些内部组件设计的。我花了几个小时才在机器人玩具内部进行切割和改造来安装这些。下面是 Raspberry PI 机器人模型的电路设计。腿部和肘部设计不在本文的讨论范围内。

4.2 语音识别

在我们的项目中实现语音识别有多种方法。当涉及到 Linux 平台时,我认为 pocketsphinx 是最好的,因为它可以在离线状态下使用,并且可以对其进行微调以提高准确性,我们还可以定义自己的语言模型。在本节中,我们不会详细讨论 pocketsphinx 的所有内容,但我会尝试涵盖我们文章所需的所有内容。本文使用的 pocketsphinx 版本是 0.8。如果您想更深入地了解此主题,请参考 Pocketsphinx 在线文档,因为本文将更侧重于我们将从 pocketsphinx 中使用的内容。

Pocketsphinx 提供了许多选项来定义我们希望它识别语音的方式。基本上,它可以分为两种语言模型:语法(jsgf, fsg, gram)和统计语言模型(lm, dmp)。本文使用语法模型,以更逼真的命令和更高的准确性来定义。语言模型也有自定义功能,因此您可以定义自己的模型来提高准确性。

JSGF 是 Java Speech Grammar Format 的缩写,是语音识别的文本形式的语法规范。JSGF 模型定义了语音识别时应遵循的语法。例如:“Hi, <question> are you?”,如果我们定义 <question> 为“how”或“where”或“what”,那么您就可以期待 Pocketsphinx 识别出“Hi, how are you?”或“Hi, where are you?”或“Hi, what are you?”。好的,我们如何定义 JSGF 语法?

#JSGF V1.0;
grammar iobot;
public <command> = turn head (left | right | straight);

在上面的语法中,第一行是语法头 #JSGF V1.0,后面跟着语法声明 (iobot),然后是语法体,其中包含公共规则定义 <command>。使用此语法的说话者可以说“turn head left”或“turn head right”或“turn head straight”。| 符号表示 OR 条件,左右直行则用括号()组合在一起。我们也可以定义一个可选条件,如下所示:

public <command> = [please] turn head (left | right | straight)

在上面的语法中,说话者可以选择在他的句子中使用 please。好的,我们的 PI Robot 语法是如何定义的?

#JSGF V1.0;
grammar iobot;
public <commands> =  (<eyes> | <hands> | <head> | <other> | (new command) | (finish) | ([ok] learn <other> | forget <other> | remove <other> | (enable | disable) sharing));
<eyes> = ((close | open) your (eyes | ((left | right) eye)));
<hands> = ((raise | let) your (hands | ((left | right) hand)) [down]);
<head> = (turn head (left | right | straight));
<other> = ( refresh commands | wink );

上面的语法试图组合所有可能的命令,以便我们的 PI Robot 理解基本命令。<other> 规则将用新命令更新,语法将在 ps 解码器中刷新。<eyes>、<hands> 和 <head> 规则被认为是机器人通常会执行的基本命令,而 <other> 规则中定义的命令是机器人从我们那里学到的扩展命令。

为语音识别编写的 C++ 类代码 (BOTS2T.cpp) 只是从 pocketsphinx 附带的 pocketsphinx_continuous 源码扩展而来。因此,麦克风录音、识别、假设等所有功劳都归功于 pocketsphinx。但并非所有功劳都是如此,该类源还处理多个语法文件并进行语法切换。此外,该类初始化一个单独的线程进行语音识别,因此整个识别过程在一个不同的线程中无限期地运行,直到程序结束。

4.2.1 在 Raspberry PI 上安装 Pocketsphinx

在 Raspberry Pi 上安装 pocketsphinx 很简单,但可能有点耗时,但值得等待。请按照以下命令在您的 raspberry pi 上设置 pocketsphinx:

wget http://sourceforge.net/projects/cmusphinx/files/sphinxbase/0.8/sphinxbase-0.8.tar.gz/download
mv download sphinxbase-0.8.tar.gz

wget http://sourceforge.net/projects/cmusphinx/files/pocketsphinx/0.8/pocketsphinx-0.8.tar.gz/download
mv download pocketsphinx-0.8.tar.gz

tar -xzvf sphinxbase-0.8.tar.gz
tar -xzvf pocketsphinx-0.8.tar.gz

apt-get install bison
apt-get install libasound2-dev

cd sphinxbase-0.8
./configure --enable-fixed
make
make install

cd ..
cd /pocketsphinx-0.8/
./configure
make
sudo make install

好吧,在完成以上所有步骤后,如果您能运行 ./pocketsphinx_continuous 并看到“READY”,那么您就可以进行语音识别了。如果您在使用语音识别时遇到任何问题,可能是您的麦克风设置有问题,请使用 alsamixer 检查您的麦克风是否被检测到并正确安装。

4.2.2 使用 Pocketsphinx 编程

配置对象是初始化 pocketsphinx 解码器的强制参数,此配置设置包含初始化 pocketsphinx 解码所需的各种参数。主要是我们的 JSGF 文件。 cmd_ln_init() 函数接受可变参数,因此您可以根据需要添加参数(仅支持 ps 解码器支持的参数),它会返回 ps_init() 调用所需的配置对象。下面的代码使用我们定义的 JSGF 语法文件 iobot.jsgf 初始化我们的配置对象。

//cmd_ln_t *m_config;

m_config = cmd_ln_init(NULL, ps_args(), TRUE,
                         "-hmm", hmm.c_str(),
                         "-jsgf", "./iobot.jsgf",
                         "-dict", lm.c_str(),
                         NULL);

初始化我们的 pocketsphinx 解码器是一行代码:

//ps_decoder_t *m_ps;

m_ps = ps_init(m_config);

如果以上两个调用都成功,那么我们的解码器就准备好进行语音识别了。您可以通过调用 ps_start_utt(m_ps) 来开始发音,然后调用 ps_process_raw(m_ps,...) 来处理原始音频数据,然后调用 ps_end_utt(m_ps) 来停止发音,最后调用 ps_get_hyp(m_ps,...) 来获取识别到的单词。我建议您参考 Pocketshpinx 在线文档和示例以获得更多理解。如前所述,您也可以参考 pocketsphinx_continuous 源代码以获得更多理解。

我们的 PI 机器人除了语音识别还需要做的另一件重要事情是语法切换。Pocketsphinx 解码器允许在运行时使用 fsg_set_add() 函数(BOTS2T::Init())添加多个语法,但一次只能有一个语法处于活动状态。fsg_set_select() 函数允许选择或设置解码器的活动语法(BOTS2T::SetGrammar())。类似于 add 函数,我们还可以使用 fsg_set_remove_byname() 函数从解码器中删除语法(参考:BOTS2T::RemoveGrammar())。下面是向解码器添加 iobot.jsgf 的代码快照。

    fsg_set_t* fsgset=ps_get_fsgset(m_ps);
    jsgf_t* jsgf=jsgf_parse_file ("./iobot.jsgf",NULL);
    jsgf_rule_iter_t* iter=jsgf_rule_iter (jsgf);
    jsgf_rule_t* rule=NULL;
    while(jsgf_rule_iter_rule(iter)) {
        if(jsgf_rule_public (jsgf_rule_iter_rule(iter))) {
            rule=jsgf_rule_iter_rule(iter);
            break;
        }
        jsgf_rule_iter_next(iter);
    }
    jsgf_rule_iter_free(iter);
    fsg_set_add (fsgset,"iobot",jsgf_build_fsg (jsgf,rule,ps_get_logmath(m_ps),cmd_ln_int32_r(m_config, "-lw")));
    jsgf_grammar_free (jsgf);

上面的代码尝试查找 iobot.jsgf 中公共规则的第一个出现,然后将其添加到运行中的 ps 解码器的 fsg 集合中。但是,我们已经知道我们的语法名称 (iobot) 和我们要使用的公共规则名称 (command),我们可以通过直接给出语法名称和规则名称来构建 fsg,从而简化上面的代码,如下所示:

fsg_set_t* fsgset=ps_get_fsgset(m_ps);
jsgf_t* jsgf=jsgf_parse_file ("./iobot.jsgf",NULL);
fsg_set_add (fsgset,"iobot",jsgf_build_fsg(jsgf,jsgf_get_rule(jsgf, "iobot.command"), m_ps->lmath, cmd_ln_int32_r(m_config, "-lw")));
jsgf_grammar_free (jsgf);

我们可以在运行时不断向解码器添加多个语法。多个语法并在运行时切换有什么用?有很多,特别是我们的 PI 机器人使用不同的语法来学习新命令。由于我们为机器人的语音识别定义了一个语法集,如果您想教一个新词,机器人将无法理解。所以,为此,我们需要将语法切换到一个包含所有英文单词的通用语言模型文件,或者一个包含字母的语法文件来拼写新单词给我们的机器人,但无论哪种方式,我们的机器人需要切换到其他语法或语言模型来学习新单词。

代码参考:BOTS2T.cpp/BOTS2T.h

4.3 PI 机器人类

既然我们已经知道伺服电机是如何工作的以及如何为我们的机器人添加语音识别,那么剩下的事情就是将识别到的语音转换为文本,并处理文本以查找已知命令,然后根据识别到的命令旋转机器人上的伺服电机,或者点亮 LED 来模仿眨眼/睁眼动作。

Robot robot;

robot.Init("iobot.azurewebsites.net", /* server */
        6 /*left eye*/,
        12 /*right eye*/,
        27 /*left hand*/,
        18 /*right eye*/, 
        17 /*head*/
        );
    robot.InitSpeechRecognition("./iobot.jsgf","./iobotalpha.jsgf");
    robot.AddAction("raise your left hand", LEFT_HAND_UP);
    robot.AddAction("raise your right hand", RIGHT_HAND_UP);
    robot.AddAction("let your left hand down", LEFT_HAND_DOWN);
    robot.AddAction("let your right hand down", RIGHT_HAND_DOWN);
    robot.AddAction("close your eyes", CLOSE_LEFT_EYE);
    robot.AddAction("close your eyes", CLOSE_RIGHT_EYE);
    robot.AddAction("open your eyes", OPEN_LEFT_EYE);
    robot.AddAction("open your eyes", OPEN_RIGHT_EYE);
    robot.AddAction("open your right eye", OPEN_RIGHT_EYE);
    robot.AddAction("open your left eye", OPEN_LEFT_EYE);
    robot.AddAction("close your right eye", CLOSE_RIGHT_EYE);
    robot.AddAction("close your left eye", CLOSE_LEFT_EYE);
    robot.AddAction("turn head left", LOOK_LEFT);
    robot.AddAction("turn head right", LOOK_RIGHT);
    robot.AddAction("turn head straight", LOOK_STRAIGHT);
    robot.Start();

上面的代码只是展示了如何使用 Robot.cpp/h 中定义的 Robot 类。这些函数很容易理解。 Init() 函数用于初始化 GPIO 引脚以使用 LED 和伺服电机,InitSpeechRecognition() 用于使用给定的 jsgf 语法文件和用于处理带麦克风的语音识别的线程来初始化 pocketsphinx 对象,AddAction() 用于将命令值映射到从语音识别的命令字符串,最后,Start() 是一个无限循环,持续监听命令并执行操作,直到我们按下 Ctrl+C,这将由 sigaction() 处理以释放我们正在使用的对象。

代码参考:ROBOT.cpp/ROBOT.h

4.4 进行网络调用

到目前为止我们讨论的都是关于机器人识别语音命令并执行其内部定义的基本动作,例如举手、睁眼等。但我们的目标不仅仅是这样,很快我们将详细讨论 Microsoft Azure 和 WebAPI,我们的机器人必须学会与其他机器人共享命令。我们将让我们的机器人进行网络调用来访问 Azure 服务。下面是如何通过套接字实现这一点的代码快照:

int Robot::CallWebAPI(string server, string verb, string function, string userid, string param, string &outData)
{
    int status;
    struct addrinfo host_info;       
    struct addrinfo *host_ilist; 
    memset(&host_info, 0, sizeof host_info);
    host_info.ai_family = AF_UNSPEC;     
    host_info.ai_socktype = SOCK_STREAM; 

    string uri="/api/"+ function + "/" + userid;
    if(param!="") uri += "?" + param;
    printf("\n%s : %s --> %s\n",verb.c_str(), server.c_str(), uri.c_str());

    status = getaddrinfo(server.c_str(), "80", &host_info, &host_ilist);
    if (status != 0)  return -1;
    int skt ; 
    skt = socket(host_ilist->ai_family, host_ilist->ai_socktype,
                      host_ilist->ai_protocol);
    if (skt == -1)  return -2;

    status = connect(skt, host_ilist->ai_addr, host_ilist->ai_addrlen);
    if (status == -1)  
    {
        close(skt);
        return -3;
    }
    
    string msg = verb+" "+uri+" HTTP/1.1\nhost: "+server+"\ncontent-length:0\nConnection:Close\n\n";

    ssize_t bytes_sent;
    bytes_sent = send(skt, msg.c_str(), msg.length(), 0);
    ssize_t bytes_recieved;
    char buffer[1001];
    outData="";
    int received=recv(skt, buffer,1000, 0);
    while ( received > 0 ) {
        buffer[received]='\0';
        outData+=buffer;
        received=recv(skt, buffer,1000, 0);   // see if we have got some more data 
    }
    printf("\n%s\n",outData.c_str());
    freeaddrinfo(host_ilist);
    close(skt);
}

上面的代码只是非常熟悉、直接、经典、老式的套接字功能,我们一直使用并且仍然在使用它与服务器通信。getaddrinfo() 调用用于获取给定服务器地址(例如 google.com)的地址信息,然后使用 socket() 调用使用地址信息创建套接字。connect() 函数使用创建的套接字建立与服务器的连接。然后,我们构建要发送到服务器的数据。对于 GET/PUT/DELETE 方法调用,数据格式将是相同的(server/uri?querystring)。对于 POST 方法调用,我们需要构建标头、内容类型、长度、数据体等。我们不期望此函数进行 POST 方法调用,因此 content-length 设置为 0。在构建要发送到服务器的数据后,该函数使用 send() 调用和创建的套接字将数据发送到服务器。嗯,我们的机器人应该从服务器接收一些数据,对吧?所以,recv() 函数接收来自服务器的响应数据。最后,close() 函数调用用于关闭套接字。所有函数都是典型的套接字连接功能,我们只需要知道如何根据我们要使用的方法/动词来构建数据。

现在,我们的机器人可以像这样进行网络调用来获取 Azure 的操作:

string param="command="+command;
CallWebAPI(m_server,"GET","IOBOTActions", m_uid,param,result);

其中 m_server 是 iobot.azurewebsites.net,“GET”是我们使用的方法,IOBOTActions是我们期望调用的函数,m_uid 是我们机器人的 ID,param 是 GET 的查询字符串,它携带命令,result 是来自服务器的数据响应。因此,通过这个调用,我们正在询问我们的 Web 服务,该“命令”的操作代码是什么?然后我们从 Web 服务中获得“result”中的操作代码。好吧,屏住呼吸,在深入 Azure 主题之前,还有一个部分需要介绍。

代码参考:ROBOT.cpp/ROBOT.h

4.5 编译代码

下载 PIRobot 源代码并将其提取到 PI 设备上的单独文件夹中,然后运行以下命令:

g++ -o iobot iobot.cpp ROBOT.cpp BOTS2T.cpp BOTGPIO.cpp BOTServo.cpp -lpthread  -I/usr/local/include/sphinxbase -I/usr/local/include/pocketsphinx  -lpocketsphinx -lsphinxbase -lsphinxad

确保您已完成所有机器人电路设置,并通过运行 ./iobot. 来执行机器人。

5. Intel Galileo 机器人模块

我们的 Intel Galileo 机器人模块与 Raspberry PI 模块不同,因为我在这个 Intel 板上使用了 Windows。所以,让我们先简要介绍一下如何在 Intel Galileo Gen2 上设置 Windows 操作系统。

5.1 在 Intel Galileo Gen 2 上设置 Windows

  1. 从这个链接下载 Gen2 Windows 映像 Windows 映像 for Galileo Gen 2
  2. 从这个链接下载 apply-BootMedia.cmd apply-BootMedia.cmd。这是为了在您的 MicroSD 卡上创建映像。
  3. 使用 FAT32 格式化 MicroSD 卡
  4. 以管理员模式打开命令提示符并运行以下命令(根据您的需要替换和更改参数):
apply-bootmedia.cmd -destination {YourSDCardDrive} -image {.wimFile} -hostname {hostname} -password {password}
{YourSDCardDrive} - 您的 MicroSD 卡的驱动器号(例如:E:)
{.wimFile} - 我们从第 1 点下载的 Windows 映像文件。
{hostname} - 您的 Intel Galileo 的主机名
{password} - 默认 Administrator 帐户的密码。

在 Windows 7 上,apply-bootmedia.cmd 无法直接工作,您需要下载并安装 Windows 8.1 的 Windows Assessment and Deployment Kit。这在 Windows 7 上安装耗时很长,所以启动它,然后做些别的事情。安装完成后,在记事本中打开 apply-bootmedia.cmd,并将“%SystemRoot%\System32\Dism.exe”替换为您计算机上 Dism.exe 的完整路径(从 ADK 安装)。有关更多详细信息,请参阅此 链接

  1. 在您的计算机上下载并安装 Visual Studio 2013 Express for Desktop(或完整版本)。
  2. 下载并安装 Windows Developer Program for IOT
  3. 几乎完成了,将 MicroSD 卡插入 Galileo Gen 2 板,连接以太网线、电源线并打开电源。哦,在哪里可以看到板上发生的事情?在您的计算机上打开命令提示符,键入“telnet {hostname}”。您甚至可以先尝试 ping {hostname} 来确保 Galileo 已启动并运行。

Telnet 会提示输入用户名和密码。用户名是“Administrator”,密码是您在创建 MicroSD 映像期间使用 apply-bootmedia.cmd 时输入的 {password}。通常,仅出于一个原因,建议使用“mygalileo”作为主机名。“Windows Developer Program for IOT”在 Visual Studio 中提供了一个项目模板,该模板默认配置了与“mygalileo”名称的远程调试。因此,如果您想使用不同的主机名,请在项目属性 --> 配置属性 --> 调试 --> 远程服务器名称中更改“远程服务器名称”以您偏好的 {hostname}。

正如我之前提到的,Windows Developer Program for IOT 的安装会在 Visual Studio 中为您提供一个模板,而我们将从那里开始我们的 Intel Galileo Gen 2 Windows 编程。运行 Visual Studio 2013 for Desktop --> 新建项目 --> 模板 | Visual C++ | Windows for IoT --> Galileo Wiring App。给项目命名,选择位置,设置解决方案名称,然后单击 OK。您将在屏幕上看到模板已编写的默认代码。打开 Main.cpp,如果您以前做过 Arduino 编程,您会看到非常熟悉的代码,

int _tmain(int argc, _TCHAR* argv[])
{
    return RunArduinoSketch();
}
void setup()
{
}
void loop()
{
}

代码的这种格式只是对 Arduino 编程方法的模仿,您也可以跳过这些,并像这样编写您的代码:

int _tmain(int argc, _TCHAR* argv[])
{
    /// your setup() code here //
    while(true)
    {
          // your loop() code here //
    }
    return 0;
}

您现在可以进行 pinMode()digitalWrite() 调用,然后单击“远程 Windows 调试器”在 Intel Galileo Gen 2 板上运行您的程序。请记住一点,“它是 Windows”,任何突然关闭都可能导致启动过程延迟,因此请务必始终正确关闭 Galileo(带 Windows),为此,请按照前面解释的方式打开 Telenet 并键入“shutdown /p”。

5.2 Galileo 机器人设计

Galileo 机器人的设计与 PI 机器人非常相似,但略有不同,因为它没有连接麦克风或扬声器,而是从 Android 手机接收命令,我们将在 Azure Mobile Service 部分介绍如何做到这一点。下面是 Galileo 机器人的简单设计,

Galileo 机器人也使用了与 PI 中相同的 Servo 类,这只是它的 Windows 版本,上面运行着 Windows 线程。否则,机器人代码非常相似,如下所示:

    Robot robot;

    robot.init(L"iobot.azurewebsites.net",  /*server*/
        12,        /*left eye*/
        13,        /*right eye*/
        6,         /*left hand*/
        5,         /*right hand*/
        4          /* head */
        );
    robot.Eyes(OPEN);
       .
       .
       .

void doCommand(char* command)
{
    // add validation here to see whether it is really a numeric value
    switch (atoi(command))
    {
        case LEFT_HAND_UP:          robot.MoveLeftHand(0);    break;
        case LEFT_HAND_DOWN:        robot.MoveLeftHand(180);    break;
        case LEFT_HAND_STRAIGHT:    robot.MoveLeftHand(90);    break;
        case RIGHT_HAND_UP:         robot.MoveRightHand(180); break;
        case RIGHT_HAND_DOWN:       robot.MoveRightHand(0); break;
        case RIGHT_HAND_STRAIGHT:   robot.MoveRightHand(90); break;
        case TURN_HEAD_LEFT:        robot.TurnHead(LEFT_SIDE); break;
        case TURN_HEAD_RIGHT:       robot.TurnHead(RIGHT_SIDE); break;
        case TURN_HEAD_STRAIGHT:    robot.TurnHead(STRAIGHT); break;
        case CLOSE_LEFT_EYE:        robot.LeftEye(CLOSE); break;
        case CLOSE_RIGHT_EYE:       robot.RightEye(CLOSE); break;
        case OPEN_LEFT_EYE:         robot.LeftEye(OPEN); break;
        case OPEN_RIGHT_EYE:        robot.RightEye(OPEN); break;
        case CLOSE_BOTH_EYES:       robot.Eyes(CLOSE); break;
        case OPEN_BOTH_EYES:        robot.Eyes(OPEN); break;
    }
}

5.3 进行网络调用和 C++ REST SDK

PI 机器人模块使用套接字连接到 Web 服务,而 Galileo 模块使用 WinHTTP API,通过 WinHttpOpen()WinHTTPConnect()WinHTTPOpenRequest()WinHTTPSendRequest()WinHTTPReceiveResponse()。这些函数调用没有什么太多需要解释的,因为它们在 MSDN 上已经存在了十多年。函数 Robot::CallWebAPI() 使用 WinHTTP API 调用来对 Web 服务执行 GET、PUT 请求。但是,总有不同的方法来完成同一项工作,下面是可用于 Galileo 的 HTTP 连接的另一种方法,即 C++ REST SDK。在撰写本文时,可用于 Intel Galileo 的 REST SDK 版本是 2.2。因此,在您的 Visual Studio 2013 中,转到工具-->NuGet 包管理器-->包管理器控制台并输入“Install-package cpprestsdk -Version 2.2.0”来为您的项目安装 REST SDK 版本 2.2。通过在您的项目中拥有 NuGet 包,您现在可以编译您的项目而不会出现任何错误。但是,如果您将 exe 部署到 Galileo,您将收到“dependency dll error”。为此,您需要进行一个小小的解决方法才能使 REST SDK 在我们的 Galileo 上正常工作。解决方法如下:

  1. 下载 Casablanca REST SDK 版本 2.2.0 源代码。
  2. 在 Visual Studio 2013 中打开 casablanca120.desktop.sln。
  3. 转到 casablanca120 解决方案属性-->配置属性-->C/C++-->代码生成-->启用增强指令集。在 Debug 和 Release 模式下,将“Not set”更改为“No Enhanced Instructions (/arch:IA32)”。
  4. “Rebuild All” casablanca120 解决方案。
  5. cpprest120d_2_2.dllcpprest120_2_2.dll 分别从 Debug 和 Release 文件夹复制到 Galileo 的 C:\test 文件夹。“C:\test”是 Galileo Windows 上默认的开发部署文件夹。您可以在项目属性中更改它。注意调试 dll 名称中的“d”字符。

完成!现在您的 Galileo 已准备好进行 C++ REST SDK 编程。下面是在 Galileo 上使用 http_client 进行网络调用的 REST SDK 方法。

void Robot::CallWebAPIREST(const wchar_t* pszServer, const wchar_t* pszURL, wchar_t* pszData, wchar_t* pszResponseData)
{
    std::wstringstream url;
    url << L"http://" << pszServer << L"/" << pszURL;
    if (wcscmp(pszData, L"") != 0) url << "?" << pszData;
    web::http::client::http_client client(url.str());
    client.request(web::http::methods::GET).then([pszResponseData](web::http::http_response response)
    {
        if (response.status_code() == web::http::status_codes::OK)
        {
            utility::string_t result = response.extract_string().get();
            wcscpy(pszResponseData, result.c_str());
        }
    });
}
代码参考:ROBOT.cpp/ROBOT.h

6. Microsoft Azure

PI 和 Galileo 机器人现在已经准备好执行基本命令了。但是,它们将如何共享命令呢?在本文的这一点上,我们讨论了如何制造机器人以及如何进行网络调用。所以,接下来我们需要讨论的剩下的事情其实并不少——Microsoft Azure,用于托管我们 Web 服务的云平台,以帮助我们的机器人共享命令,事实上,我们的 Galileo 机器人只有在有一个 Web 服务来帮助它接收命令时才能工作。Microsoft Azure - 在本节的标题下,我们将讨论以下主题:

  1. Microsoft Azure 的平台即服务 (PaaS)
    • Azure SQL 数据库用于存储我们的机器人命令
    • Azure 网站用于我们的 Web API
    • ASP .NET MVC Web API 用于 Azure 网站
    • Azure Mobile Service 用于 Android
  2. Microsoft Azure 的基础设施即服务 (IaaS)
    • 虚拟机上的 Windows 2012 Server
    • 在虚拟机上托管 Web API

6.1 PaaS - Azure SQL 数据库

创建 SQL 数据库不再是件难事,只需在 Azure 上点击几下即可。借助 Azure 的平台即服务,不再需要物理服务器维护,甚至不需要虚拟服务器维护。为了让我们的机器人存储和共享命令,我们需要创建一个数据库。创建 Azure SQL 数据库:

点击“Custom Create”选项后,这是一个两步过程。第一步,给数据库命名,选择“New SQL database server”作为服务器,除非您确实需要特定的配置,否则将其余选项保留为默认值。第二步,为您的数据库输入登录名和密码,选择您认为数据库服务应该可用的区域。完成!您已经创建了一个数据库,并且它也在您首选的位置运行。Azure 创建您的数据库实际上需要几分钟时间。实际上,您的数据库运行在一个服务器上,您可以在“SQL Databases”主页上找到服务器的详细信息。您可能需要了解这一点,因为还有一些事情需要您处理,例如数据库备份、允许访问您数据库的 IP 地址等。

 

要对您创建的数据库进行进一步操作,请下载并安装 SQL Management Studio 2012 或最新版本。通过提供“Server”名称作为“{servername}.database.windows.net”、“Authentication”作为“SQL Server Authentication”、登录名作为您在 Azure SQL 数据库上“给出的名称”以及密码作为您在 Azure SQL 数据库上“给出的密码”,最后点击 Connect 来连接到我们创建的 Azure SQL 数据库。如果您遇到错误,请不要感到惊讶,这可能是您的 Azure SQL 数据库的 IP 访问问题。转到 Azure --> SQL DATABASES --> Servers --> 单击您的 SQL Server --> Configure。您可能会看到已列出尝试的 IP 地址,或者您可以输入 IP 地址在可用框中允许数据库服务器访问该 IP,并且不要忘记点击页面底部的“Save”。

 

现在,是时候在我们的数据库中创建一些表了,这与使用 SQL Management Studio 创建任何 SQL 数据库表一样简单。右键单击数据库名称以打开查询窗口,或右键单击“Tables”通过向导创建表。让我们创建几个小型表,我们的机器人可以在这些表上存储数据。

ROBOT 表,用于 PI 机器人和 Galileo 机器人查找它们的基本命令之外的通用命令。COMMAND 是机器人将查找的字符串值,ACTIONS 是逗号分隔的整数值,每个值代表一个基本操作。USER_ID 是给机器人的 ID。

CREATE TABLE [dbo].[ROBOT](
    [CID] [int] IDENTITY(1,1) NOT NULL,
    [COMMAND] [nvarchar](max) NOT NULL,
    [ACTIONDATA] [nvarchar](max) NULL,
    [USER_ID] [varchar](20) NULL,
    [SHARE_TYPE] [int] NULL,
 CONSTRAINT [PK_ACTIONS] PRIMARY KEY CLUSTERED 
(
    [CID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)

USERDATA 表用于机器人 ID 管理,还有一个 Access code 列,用于访问机器人管理 Web 门户。此访问码在创建后 2 分钟内过期。要创建访问码,我们需要与我们的机器人交谈。例如,要获取 PI 机器人的访问码,请问它“Give me an access code”,您将获得一个。

CREATE TABLE [dbo].[USERDATA](
    [CID] [int] IDENTITY(1,1) NOT NULL,
    [USER_ID] [varchar](20) NOT NULL,
    [ACCESS_CODE] [varchar](20) NOT NULL,
    [ACODE_EXP] [datetime] NULL,
    [DISPLAY_NAME] [varchar](20) NULL,
 CONSTRAINT [PK_USERDATA] PRIMARY KEY CLUSTERED 
(
    [USER_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)

就是这样,我们创建了一个 Azure SQL 数据库和一些表,我们的机器人可以使用它们。现在,是时候创建一些 Web API 来访问我们数据库中的数据供我们的机器人使用了。

6.2 PaaS - Azure 网站和 ASP.NET MVC Web API

在创建 ASP.NET MVC Web API 项目之前,我们需要创建一个 Azure 网站,我们将在该网站上托管我们的 Web API。正如我之前为数据库所说的,创建网站也只需点击几下。事实上,它比这更简单。转到您的 Azure 门户 --> 点击页面底部的 New --> Compute --> Web App --> Custom Create。输入一个 URL 名称 "{urlname}.azurewebsites.net",选择您偏好的区域(如果您愿意,可以选择一个数据库进行链接)。就这样,您将拥有一个已创建并运行的网站。点击您创建的网站,然后转到“Configure”以检查您网站的所有配置项,如应用程序支持(.NET, PHP 等)、默认文档(index.htm, Default.asp 等)以及更多内容。

现在,让我们为机器人创建 ASP.NET MVC Web API,以便它们可以访问 Azure 数据库。为了创建 Web API 项目,我使用了“Visual Studio 2013 Express for Web”,它是免费的。打开 Visual Studio Express 2013 for Web 并创建一个“New Project”,选择“ASP.NET MVC 4 Web Application”,

在“New ASP.NET MVC 4 Project”中选择“Web API”项目模板,然后单击 OK。

现在,您将看到默认生成的代码,在 ValuesController.cs 下有 Get、Put、Post 和 Delete 函数。听起来是不是很熟悉?是的,它们与我们在 HTTP 协议中使用的 GET、PUT、POST 和 DELETE 方法/动词相同。此项目将被部署为 Web 包到我们刚刚创建的 Azure 网站,并且对我们提供的 Web API URL 的任何 GET、PUT、POST 和 DELETE 方法调用都将在内部由这些函数处理。所以,现在工作很简单,不是吗?只需将所有需要的代码写入这些函数中,并让我们的机器人知道调用 GET、POST 等的 Web URL。没错,这就是我们要做的!但是,我们还需要了解如何从这些函数访问 Azure SQL 数据库。这才是重点,对吧?在深入研究之前,让我们先看看如何使用这些函数。尝试运行项目,而不修改任何代码。请注意,您可以选择您偏好的浏览器来运行项目。

您将看到 localhost 正在运行,并带有调试器选择的端口号,您还可以看到默认的 ASP.NET Web API 主页。现在,尝试将 URL 修改为“https://:{portnumber}/api/Values”并回车。您是否在浏览器中看到了显示“value1”和“value2”的 XML 或 JSON 结果?很好,这就是我们在这里需要理解的。类名“ValuesController”告诉您,“Values”是该类函数在去除“Controller”后的 API 名称。所以,向“https://:{portnumber}/api/Values”发出 GET 请求将调用 ValuesController 类函数,由于这是 GET 请求,默认的 Get() 函数将被调用。要调用 Get(int id) 函数,URL 如下“https://:{portnumber}/api/Values/1”,使用此 URL 发出 GET 请求将调用 Get(int id) 函数,其中 id 的值为 1。此外,如果您想传递更多参数怎么办?让我们现在稍微修改一下代码,添加几个额外的 Get() 函数,如下所示,添加到您的默认代码中:

public string Get(int id, string val)
{
    return "Get with 2 params called";
}

public string Get(int id, string val1, string val2)
{
    return "Get with 3 params called";
}

现在,URL 需要传递查询字符串才能调用这些函数。请参见下表,了解函数和 Web URL 映射的示例。

所以我们知道 Controller 在我们的 Web API 项目中的作用。现在,让我们回到之前的问题,如何在这些函数中连接到 Azure SQL 数据库。为此,我们需要了解 Microsoft 的“Entity Framework”。我们不会深入探讨它是什么,但根据定义,这是 Microsoft 所说的:

"Entity Framework (EF) 是一个对象关系映射器,它使 .NET 开发人员能够使用特定于域的对象处理关系数据。它消除了开发人员通常需要编写的大部分数据访问代码。"

我们将创建一个 Entity 类,它将简化我们连接到数据库的工作。我们将这样做:右键单击“Models”文件夹或解决方案资源管理器中的项目,然后选择“Add”-->“Class”。

在“Visual C#”下选择“Data”,然后选择“ADO.NET Entity Data Model”,为您的 Entity Model 命名,然后单击 OK。

现在您将看到“Entity Data Model Wizard”。选择“EF Designer from Database”,因为我们将从已设计的数据库构建模型类。单击“Next”,您将看到我们正在寻找的最重要的页面 -“Data Connection”。单击“New Connection”,这将弹出“Connection Properties”窗口,在其中输入您的 Azure SQL Database 服务器名和连接详细信息:

您的数据库服务器将列在“Entity Data Model Wizard”的“Choose your data connection”页面上。选择“Yes, include the sensitive data in the connection string”,将“Save connection settings in Web.Config as”保持选中状态,并为 Entities 命名,然后单击“Next”。您可能会被要求选择 Entity Framework 版本,在本例中,将其保留为 Entity Framework 5.0 并继续 Next。我们现在将看到的页面是“Choose your Database Objects and Settings”。在这里选择您想要为其创建模型类的数据库表。

就是这样。我们创建了一个 Entity Model 类,它可以代表我们 Web API 函数中的 Azure SQL 数据库。让我们看看如何将这些模型类用于我们的 Web API。默认的 Container (ValuesContainer) 对我们来说不够有用,因为我们需要我们自己的 API,具有正确的名称和我们创建的 Entity Model,对吧?所以,让我们创建我们自己的 Container,并使用我们创建的 Entity Model。在此之前,先将项目重新生成一次。在解决方案资源管理器中右键单击“Controller”文件夹--> Add --> Controller。为您的 Controller 命名。请记住,这将是您 URL 中的 Web API 名称(不包括“Controller”一词)。选择“API controller with read/write actions, using Entity Framework”,然后选择要为其创建 Controller 的表的模型类。最后,选择 Data context,也就是您在“Entity Data Model Wizard”的“Choose your data connection”页面上给出的名称。

单击 OK,您将看到新创建的 Controller,其中包含完整的 GET、PUT、POST 和 DELETE 函数的类代码。注意在第一个 Get() 函数之前添加的额外一行,即实体类和对象,这将是我们的数据库代表,通过它我们将进行数据库查询、插入、删除等操作。

       private IOBOTDBEntities db = new IOBOTDBEntities();

        // GET api/ROBOT
        public IEnumerable<ROBOT> GetROBOTs()
        {
            return db.ROBOTs.AsEnumerable();
        }

        // GET api/ROBOT/5
        public ROBOT GetROBOT(int id)
        {
            ROBOT robot = db.ROBOTs.Find(id);
            if (robot == null)
            {
                throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
            }

            return robot;
        }

默认代码提供了 Get/Put/Post/Delete 函数应有的功能,但您可以根据需要进行更改。以下是我们为 IOBOT 项目机器人修改的函数之一:

        public string GetROBOTs(string id)
        {         
            string commands = "";
            DbSqlQuery<ROBOT> sres = db.ROBOTs.SqlQuery("SELECT * FROM ROBOT WHERE USER_ID = @p0", id);
            if (sres == null)
            {
                return "NOK";
            }
            foreach (ROBOT robot in sres)
            {
                commands += robot.COMMAND + "|";
            }
            commands = commands.Trim(new char[] { ' ', '|' });
            return commands;
        }

注意 SqlQuery() 函数调用,参数通过 @p0 单独传递给查询。如果您想传递更多参数,请继续添加 @p1@p2 等。对于多个参数,SqlQuery() 函数的第二个参数必须是一个对象数组,如下所示:

DbSqlQuery<ROBOT> sres = db.ROBOTs.SqlQuery("SELECT * FROM ROBOT WHERE USER_ID = @p0 and COMMAND= @p1",
                new object[] { id, command });

要向表中添加新记录(如果不存在),我们的 Web API 使用 PUT:

// Put for updating records or create new record
// PUT api/IOBOTActions/5
public string PutROBOTs(string id, string command, string actions)
{
    DbSqlQuery<ROBOT> sres = db.ROBOTs.SqlQuery("SELECT * FROM ROBOT WHERE USER_ID = @p0 and COMMAND= @p1",
        new object[] { id, command });
    if (sres == null)
    {
        return "NOK";
    }

    ROBOT robot;
    if (sres.Count() == 0)
    {
        robot = new ROBOT();
        robot.USER_ID = id;
        robot.ACTIONDATA = "";
        robot.COMMAND = command;
        robot.SHARE_TYPE = 0;
        db.ROBOTs.Add(robot);
    }
    else
    {
        robot = sres.ElementAt(0);   // our query is expected to return only one record
        robot.ACTIONDATA = actions;
        db.Entry(robot).State = EntityState.Modified;
    }
    try
    {
        db.SaveChanges();
    }
    catch (DbUpdateConcurrencyException ex)
    {
        return "NOK";
    }

    return "OK";
}

注意 db.ROBOTs.Add(robot) 代码,它向我们的 ROBOT 表添加一条新记录(假设表名应为 ROBOTCommands)),而 db.SaveChanges() 将我们进行的添加/修改操作保存到我们已配置的 Azure 数据库中。

有关处理数据库表中记录添加、删除和修改的所有函数的详细信息,请参阅本文附带的项目源代码。

将 Web API 发布到 Azure 网站

Visual Studio 提供了一种简单的方法将我们的 Web API 项目发布到 Azure。右键单击解决方案资源管理器中的项目,然后单击“Publish”。选择“Publish Target”为“Microsoft Azure Websites”,您将被提示登录到您的 Microsoft 帐户。成功登录后,您可以从“Existing Websites”下的组合框中选择您之前创建的 Azure 网站。单击 OK 将下载发布到您的 Azure 网站所需的 Web Deploy 凭据。您还可以验证您的目标 URL。单击 Next,保留所有默认设置,然后再次单击“Next”,然后单击“Publish”。几秒钟后,您的 Web API 将在 Microsoft Azure 网站上上线,您可以通过 URL API 访问,如下所示:

http://iobot.azurewebsites.net/api/IOBOTActions/PI23432

其中 IOBOTActions 是控制器名称,PI23432 是 Get 函数的第一个参数值,此调用将返回 Robot ID 为 PI23432 的非基本命令。

6.3 PaaS - Microsoft Azure Mobile Service

正如我之前提到的,PI 机器人通过麦克风识别命令,而 Galileo 通过 Android 手机识别命令。因此,在本节中,我们将创建一个 Android 应用程序,该应用程序使用 Microsoft Azure Mobile Service 来连接到我们的 Azure SQL 数据库。让我们开始创建我们的 Android 项目。

设置 Android Studio 和 Mobile Service SDK

  1. 下载并安装 Android Studio
  2. 打开 Android Studio 并选择一个项目模板(Blank Activity),创建一个名为“IOBOTMobile”的项目
  3. 下载 Microsoft Azure Mobile Service SDK for Android
  4. 将 zip 文件解压到本地驱动器文件夹。
  5. 选择解压文件夹中的所有 jar 文件,然后右键单击-->复制
    • gson-2.2.2.jar,
    • mobileservices-1.1.5.jar,
    • mobileservices-1.1.5.jar.properties,
    • mobileservices-1.1.5-javadoc.jar,
    • mobileservices-1.1.5-sources.jar
  6. 在 Android Studio --> Project IOBOTMobile --> app --> libs 中,右键单击 libs -->“Paste”您复制的所有 SDK 文件。
  7. 转到 libs 文件夹下的单个 SDK jar 文件,右键单击 --> 选择“Add as library”
  8. 保存您的 Android 项目,然后单击“Sync project with Gradle Files”。

现在我们已经创建了一个 Android 项目,并在其中配置了 Microsoft Azure Mobile Service SDK 库。是时候回到 Azure 门户创建一个 mobile service 了。

点击“Create”。正如我们到目前为止在 Azure 中看到的,这是一个点击几下的过程。第一个页面用于输入您的 mobile service 的唯一 URL,选择一个新数据库或现有数据库(或一个免费的 20 MB 数据库),选择您希望托管 mobile service 的区域,并选择后端为“Javascript”,最后点击“-->”进入下一页。根据您之前的选择,第二页将要求提供数据库详细信息,在本例中,我使用了我们已创建的现有 SQL 数据库,因此该页面将要求提供我们现有数据库的登录详细信息。就这样!我们已经创建了一个 Azure Mobile Service,现在可以与我们的 Android 项目一起使用。

回到我们的 Android Studio 项目,打开项目的 Activity Class 并声明 Mobile Service Client 对象。

    private MobileServiceClient mClient;
    private MobileServiceTable<IOBOTCommand> mIOBOTCommand;

在 Activity 的 onCreate() 中添加以下代码:

        // Init Microsoft Azure Mobile Service
        try {
            // Create the Mobile Service Client instance, using the provided
            // Mobile Service URL and key
            mClient = new MobileServiceClient(
                    "https://iobot.azure-mobile.net/",     // <-- Azure Mobile Service URL
                    "yourmobileservicekeyfrommanagekeys",  // <-- Application key from Manage Keys
                    this);

            // Get the Mobile Service Table instance to use
            mIOBOTCommand = mClient.getTable(IOBOTCommand.class);
        }
        catch(MalformedURLException e)
        {
            MessageBox(e.getMessage(),true);
        }

如果您注意到,mClient 是 Mobile Service Client,它将帮助我们连接到 Azure Mobile Service。new MobileServiceClient() 接受三个参数,前两个是打开我们项目 Azure Mobile Service 的密钥。第一个是我们为 Azure Mobile Service 创建的 URL,下一个是我们可以在“Mange Keys”选项中生成的“Application Key”。

因此,有了 Mobile Service URL 和 Application Key,我们已经连接到 Azure Mobile Service。让我们开始进行表操作。转到 Azure 门户 --> Mobile Services --> 选择您的 Mobile Service --> Data --> 点击门户底部的 Create 按钮,通过给表命名来创建一个表,并将其余选项保留为默认值。转到已创建的表并单击 columns,您将看到已创建的默认列,如下所示(红色框内):

 

我们已经默认创建了“id”列,让我们在已创建的表中添加“command”作为列(上图中绿色框内)。单击“ADD COLUMN”,输入列名(command)和类型(string)。这对于 Galileo 读取来说足够了。

现在,每当用户在 Android 手机上点击按钮说出命令时,我们将在 Azure SQL 数据库中创建一个命令记录,Galileo 将在另一端读取它。我们的 Web API 将在命令被读取后删除该命令记录。因此,向创建的 Azure 表添加新命令记录如下:

if(mClient!=null && !command.isEmpty())
{
    final IOBOTCommand robotCmd=new IOBOTCommand();
    robotCmd.mCommand=command;
    robotCmd.mRobotId="32123";  // hardcoded robot id... change it to read from DB
    mIOBOTCommand.insert(robotCmd,new TableOperationCallback<IOBOTCommand>() {
    public void onCompleted(IOBOTCommand entity, Exception exception, ServiceFilterResponse response) {
            if (exception == null) {
                Show("Command Sent Successfully!",true);
            } else {
                Show("Command was not sent!",true);
            }
        }
    });
}

IOBOTCommand 对象已被创建并插入到 SQL 数据库表中。但是,如何做到的?IOBOTCommand 类是什么?它从哪里来?注意 mIOBOTCommand 对象的声明,它来自 MobileServiceTable 类,模板是 IOBOTCommand 类。IOBOTCommand 类是用于匹配我们数据库表的模板类。整个 IOBOTCommand 类如下:

package iobot.iobotmobile;

public class IOBOTCommand {
    @com.google.gson.annotations.SerializedName("command")
    public String mCommand;
    @com.google.gson.annotations.SerializedName("id")
    public String mRobotId;

    public IOBOTCommand() {

    }
}

声明了两个成员变量 mCommandmRobotId,以匹配我们 Azure Mobile Service 表中的 command 和 id 两列。因此,此类充当模板类,并与 MobileServiceTable 一起用于读取/写入 Azure 表中的数据。最后,不要忘记在 AndroidManifest.xml 文件中添加重要的条目:

<uses-permission android:name="android.permission.INTERNET" />

正如我之前提到的,用户通过手机说话来命令 Galileo,所以我们的 Android 应用程序应该能够识别语音并将其转换为文本,对吧?让我们也这样做,请看下面的代码:

findViewById(R.id.spkButton).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent i = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
                i.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, "en-US");
                try {
                    startActivityForResult(i, 1234);
                } catch (Exception e) {
                    MessageBox("Error initializing speech to text engine.", true);
                }
            }
        });

该代码使用了 Android 的语音识别 intent 类,语言模型设置为“en-US”。startActivityForResult() 调用 intent 并等待结果,我们将在 onActivityResult() 中获取它。注意 startActivityForResult() 中的请求代码,这是我们运行语音识别活动请求的 ID(1234)。它可以是任何数字,并且必须在 onActivityResult() 中检查相同的数字以获得正确的结果。thingsYouSaid 字符串数组将填充由 Speech Recognizer 识别的字符串集合。按最佳排序,最好的排在前面。

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1234 && resultCode == RESULT_OK) {
         ArrayList<String> thingsYouSaid = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            ((TextView) findViewById(R.id.textView)).setText(thingsYouSaid.get(0));
            ProcessSpokenText(thingsYouSaid.get(0).trim());
            if(bSpeakerReady) {
                Say(thingsYouSaid.get(0).trim());
            }
        }
    }

项目中还有一个 Text2Speech 代码。有关更多详细信息,请查看项目的源代码。

到目前为止,我们已经讨论了使用 Raspberry PI 和 Intel Galileo 制造机器人,以及在 Microsoft Azure 上创建各种服务供我们的机器人使用。在结束本文之前,让我们再看看 Microsoft Azure 提供的另一个服务,称为基础设施即服务(IaaS),并稍微了解一下它如何用于我们的目的。

6.4 IaaS - Windows Server 2012 虚拟机

没有比这更简单的了!只需点击几下即可创建一台具有您选择的服务器操作系统的服务器!Microsoft Azure 让这一切在您眼前实现。我们已经讨论了机器人所需的所有 Azure 内容。让我们以一种 PaaS 服务为例,看看如何在 IaaS 服务 - Azure 虚拟机中完成它。创建虚拟机,像往常一样,只需点击几下:

点击“CREATE A VIRTUAL MACHINE”后,您只需几秒钟即可在 Microsoft Azure 平台上获得一台全新、强大的服务器。现在,我们暂时不这样做。我们将通过 Visual Studio 来完成,这样会更轻松简单,因为您的虚拟机将带有 Webdeploy 功能,并且可以使发布工作更加轻松。

回到 Visual Studio 2013 中的 ASP.NET MVC Web API 项目。右键单击解决方案资源管理器中的项目名称,然后单击“Publish”进入“Publish”向导。在 Profile 部分,单击“More options”。然后单击“Microsoft Azure Virtual Machine”。您可能需要登录到您的 Microsoft Azure 帐户。通过单击“New”按钮,通过我们的 Visual Studio 创建一个虚拟机。

单击 OK 以创建我们的新虚拟机,默认启用 Web deploy 并配置了 Endpoints。等待一段时间以创建虚拟机。您可以在 Visual Studio 的 Output 窗口中查看进度。

创建完成后,再次转到 Visual Studio 中的 Publish wizard,您会注意到已经为您创建了虚拟机配置文件,以便将您的项目发布到 Azure 虚拟机。现在,在 Publish wizard 中单击 publish,然后从 Azure 网站访问您的 Web API。

要连接到虚拟机,请转到 Azure 门户 --> Virtual Machines --> 选择您的虚拟机,然后单击屏幕底部的 Connect >< 图标。您将下载一个 rdp 文件。单击并打开 rdp 文件以连接到您的虚拟机。

 

如果您想在您的计算机和虚拟机之间共享文件,有很多方法,但我发现最简单舒适的方法是通过 RDP 资源共享。转到 Run 窗口,键入 mstsc 并按 Enter。选择“Show options”并选择“Local Resource”,如下面的图片所示。最后,您将看到您选择的驱动器已映射到虚拟机。

7. 待办事项

以下是机器人的一些待办事项列表,我曾想过/正在做/将要做。但是,如果您想尝试这些:

  1. 带自平衡功能的行走功能,手指动作 - 这是一个困难的任务,但将来某个时候会完成。
  2. PI Robot 具有创建具有 2 分钟过期时间的访问码的功能。因此,每次我们需要访问机器人管理门户时,我们都必须问 PI Robot“Give me an access code”,它将告诉您一个 6 位数字的访问码来访问 Web 门户。但是 Web 门户尚未准备好。
  3. 私有、公共和受保护命令 - 想法是为机器人保留私有命令,为任何机器人都可以访问的公共命令,以及仅为友好机器人(组)保留的受保护命令。
  4. 用于人脸识别的摄像头 - 再次是困难的任务,但如果我有时间投入,可以通过额外的努力完成。
  5. PI Robot 使用字母来学习新命令,例如教它“wink”,您必须说“字母 W,字母 I,字母 N,字母 K”。这是为了避免将整个英语单词加载到识别中,这会降低准确性。但是,对于每个新命令(仅一次),您都需要使用音标字母方法来教它一个新词。我需要找到一种不同的方法来做到这一点。

8. 花絮

  • 最初的想法是只有 PI 机器人,但感谢“The Codeproject”提供的 Intel Galileo 板,我在本文中将其用作 PI 机器人的伴侣。
  • 预告视频完全使用 iPhone 摄像头和 Microsoft PowerPoint 制作。
  • Galileo 机器人计划得有点晚,所以无法在市场上买到与 PI 机器人类似的机器人(因为它已缺货)。幸运的是,从一家商店买到了最后一个有瑕疵的。因为我本来就计划把它彻底拆散,所以就拿下了。
  • 本文的最后 50% 在一周内完成,而前半部分花了 2 个月。
  • 预告视频中有一段剪辑,我说“Enable Sharing”来使 PI 机器人能够与 Galileo 共享命令,Galileo 模仿 PI 的行为。但后来在源代码中删除了这一段,以保持 Galileo 机器人独立工作并自行通过 Azure 处理命令。
  • IOBOT 这个名字被选为 Internet Of BOTs 的缩写,但恰巧,它也让人联想到《我,机器人》这部电影的名字。(预告视频的最后一部分显示 IOT 变成了 IOBOT)。

9. 结论

本文旨在解释使用 Azure 后端创建小型机器人所需的所有基本要素。机器人功能还有很多可以改进的地方,本文并不是终点。选择是开放的,您可以继续探索并自己为您的机器人添加功能。我需要休息一下,开始研究我的机器人的手指运动、行走能力等。

为了文章的简洁性,我没有对机器人和 Azure 之间传输的数据进行任何加密。但是,在实际项目中最好添加一些安全功能。感谢您花时间阅读本文。

© . All rights reserved.