Azure Insider

大全:从原始硬件到启用了云的设备

Bruno Terkaly
Steven Edouard

Bruno Terkaly and Ricardo Villalobos新一轮的淘金热又开始了,但这次寻找的不是贵金属,而是关于为用户构建创新设备并将其与云进行连接。Microsoft 新任 CEO Satya Nadella 在去年三月就曾提及“设备‘缺少云会真的很乏味’”。

这样做很有意义。如果设备通过传感器或摄像头采集信息,那么该如何且在哪儿分析这些数据呢?毕竟这些低端设备(如 Raspberry Pi)的计算能力和存储空间都非常有限。但是云及时填补了这一空白,并通过协助增强安全性和设备管理能力等得到更多的利用。因此,这个月我们希望从软硬件角度卷起袖子大干一场,并弄清真正采用这个新的计算模式需要付出什么样的代价。

智能门是物联网产品,我们将从头开始构建。我们最终会安上门铃、摄像头和 Raspberry Pi 计算设备(请参见图 1)。我们的想法是这样的:有人来到你门前,按响了门铃。他们一旦按下门铃,设备将自动拍照。然后将照片作为推送通知消息转发给移动设备(请参见图 2)。在推送的消息中将包含一个超链接,可让你查看正站在你门口的人员的图像。这个发明的灵感来自于即使你不在家,也能知道谁在按门铃的需求。

此项目中包括了门铃、摄像头和 Raspberry Pi 设备
图 1 此项目中包括了门铃、摄像头和 Raspberry Pi 设备。

接收推送通知的 Windows Phone
图 2 接收推送通知的 Windows Phone

首先,我们将说明如何购买和组装原始硬件。硬件构建完成之后,我们将构建在设备上和在 Microsoft Azure 中运行的软件。这篇文章和之后的文章都将主要介绍所需的软件。

我们需要能够拍照并将其上传至 Azure 的代码。然后从那儿将推送通知发送到相应的移动设备。这样将显示按门铃的那个人及他的照片。由于该项目的规模比较宏大,所以我们将提供一些博客文章作为辅助性支持,并讲解一些必要的细节。

与 Pi 一样简单

我们将解决在 Raspberry Pi 上和在云中(尤其是 Azure 移动服务内)运行的一些代码。在 Raspberry Pi 上,我们将安装并运行 Mono(这是一个由 Xamarin 倡导的免费开源项目),Xamarin 提供了一套兼容 Microsoft .NET Framework 的工具,其中包括一个 C# 编译器和一个 CLR。

Mono 的好处在于你可以用它编写在 Windows 上编译并在 Raspberry Pi 上原样运行的 .NET 代码。在我们的这个项目,我们将用来拍照并将其上传至 Azure 存储空间。在接下来的文章中,我们将扩展代码库以支持向 Windows Phone 发送推送通知的能力。为此,我们需要利用 Azure 服务总线中的推送通知功能并编写 Windows 应用商店或 Windows Phone 应用。

硬件组装相当简单,因为几乎所有硬件都可在一个名为 Canakit 的 Raspberry Pi 工具箱中找到。有关更多详细信息,请转到 canakit.com。此工具箱中包含了多种构建物联网设备的有用组件,其中包括系统主板、A/C 适配器、通用输入/输出 (GPIO) 接口、电路试验板、电路试验板线、各种电阻和 LED。你还需要一个外接显示器、键盘和鼠标,以便配置并测试 Raspberry Pi。除了 Canakit,你还需要另外两样东西 - 普通门铃和 Raspberry Pi 兼容摄像头,这些都很容易找到。

图 3 中的一堆硬件可能看起来很吓人,实际上却很容易装配。如果你会玩拼图游戏,就能构建类似智能门这样的装置。首先,将摄像头模块插入 Raspberry Pi 板。从袋子里取出摄像头并将排线接到 Raspberry Pi 板上。转到 bit.ly/1rk3vzk,查找有关向云发送照片的更多详细信息。

Raspberry Pi Canakit(不含摄像头)
图 3 Raspberry Pi Canakit(不含摄像头)

我们将在后面的文章中介绍门铃的完整安装方法,说明如何将其焊接到 Raspberry Pi。现在,我们连接电路试验板,它是插到 Raspberry Pi 的电路的测试版。我们可以使用跨接电缆和电路试验板模拟门铃。

其中的一个核心组件就是 GPIO 电路试验板。GPIO 是带有 26 个独立引脚(从左到右编号)的硬件电路,可让你扩展系统以与其他设备互连。通常情况下,使用这些引脚可连接传感器、执行器和 LED 等,尽管这些引脚的类型各不相同。例如,有两种为已连接设备提供电源的引脚,分别为 3.3 伏和 5 伏。还有 0 伏的引脚,可用作地接,是定义电路的必需组成部分。标记有 GPIO 的引脚充当简单的开关。若要执行序列通信,需要找到 TX 和 RX 引脚 (RS-232) 来传输和接收数据。在设备上运行的软件需要使用这些引脚进行通信。

处理这些引脚的难点之一在于软件与其通信的物理引脚与逻辑引脚之间不匹配。这与 Raspberry Pi 设备中的 Broadcom 芯片有关。这意味着,如果一个信号传输到引脚 GPIO 0,那么你的软件将切实需要读取引脚 GPIO 17,它可以是非逻辑性的,所以要特别注意。

还有其他关键的硬件组件。电阻在控制电流和降低电压水平方面起着非常重要的作用,以避免烧坏 Raspberry Pi 或其他与之连接的设备。你还需要用单芯线将 GPIO 装置连接到 Raspberry PI。

测试,测试

最终,我们需要验证正确构建了所有硬件。测试 Raspberry Pi 最简单的方法是插入外接显示器、键盘、鼠标(可选)和电源。完成之后,插入电源,将出现 Linux 启动顺序。默认用户名为“pi”,密码为“raspberry”。转到 bit.ly/1k8xBFn,了解有关设置 Raspberry Pi 的更多详细信息。

Raspberry Pi 需要其他软件,如 Mono,以运行 C# 代码。在对设备进行任何操作之前,需要先更新它的 Raspbian OS。幸运的是,Pi 为预装版本。我们要做的就是在控制台窗口中发出一些命令,将 OS 更新到最新版本。然后安装 Xamarin 的 Mono runtime 和一些受信任的根证书。这样设备才可以发送 HTTPS 请求。所有这些步骤在博客文章 bit.ly/Unehrr 中都有所解释。

软件方面

有两层需要进行编码,分别为客户端层(Raspberry Pi 设备本身)和服务层(Azure 存储空间和 Azure 移动服务)。有多种语言和后端服务可供选择。我们将在客户端上运行 C# 并在服务器上运行 Node.js/JavaScript。这是一个有争议的选择,因为有些人认为两种语言会让执行变得复杂起来。

我们的目标是展现多样性。在 Node.js 的上下文中,我们认为 npmjs.org 中的库和数据包可能让你的服务器代码更简单。Node.js 经常会产生更小的代码库,因为这些库的功能是如此强大。用第二语言编写较少的代码要比用一种语言编写的大量代码更好。图 4 中的图表突出显示了预期工作流,可归结为三步。

  1. 客户端请求共享访问签名 (SAS) 令牌,其显示为一个 URL。
  2. 在 Azure 移动服务内运行的 Node.js 将返回一个 SAS URL。
  3. 客户端将使用此 SAS URL 将照片作为 blob 上传至 Azure 存储空间。

智能门项目的高级体系结构图解
图 4 智能门项目的高级体系结构图解

我们托管了 Azure 移动服务内的 Node.js 应用程序。这就为移动设备客户端提供了许多预构建的支持,如推送通知、CRUD 存储集成、识别和构建自定义 API 的能力。

聚焦服务

我们将从构建服务开始,因为如果没有要编码的服务层,它将需要开发客户端。除非你拥有功能全面的后端服务,否则无法真正运行并测试客户端。Azure 移动服务将托管我们的服务 API。

此自定义 API 将基于 Node.js 和 Express Web 框架。我们的客户端 (Raspberry Pi) 将使用这些服务请求 SAS。然后服务将使用令牌上传存储到 Azure 存储空间的照片。使用 SaS URL 帮助保护我们的 Azure 存储空间账户,因为我们将不需要在 Raspberry Pi 中存储账户凭据。

我们在之前的文章中提到过安全问题 (msdn.microsoft.com/magazine/dn574801)。SAS 令牌之所以好还有另一个原因:它们可使中层 (Node.js API) 免于中断文件从客户端到存储空间的传输。

配置移动设备和存储空间

只需在 Azure 门户中执行一些单击操作,即可配置 Azure 移动服务。配置 Azure 存储空间也是如此。至于被配置的内容,你需要考虑 URL 和要为基于云的服务使用的命名约定。另外,还应考虑要使用哪个全球数据中心、确保你的存储空间账户位于与 Azure 移动服务相同的数据中心,以便尽量减少延迟,节省数据传输成本。有关配置 Azure 移动服务和 Azure 存储空间账户更详细的步骤,请访问 bit.ly/WyQird

共享访问权签名

图 4 所示,Raspberry Pi 客户端将请求 Azure 移动服务发放 SAS 令牌。通过从我们在 Azure 移动服务内定义的 API 发送 get 请求。SAS 令牌可使中层 (Node.js API) 免于中断文件从客户端到存储空间的传输。

图 5 中的代码是在 Azure 移动服务内以 API 服务端点运行的 Node.js 的摘录。目的在于响应需要 SAS 的 Raspberry Pi 客户端的请求。Raspberry Pi 将向托管在 Azure 移动服务中的端点发布 get 请求,传入应用程序密钥(唯一代表移动服务的标识符)。

图 5 Microsoft Azure 移动服务 API 的 Node.js 代码

exports.get = function(request, response) {
  // These are part of "App Settings" in the configuration section
  // of your service. Shouldn't be hardcoded into this Node.js script.
  containerName = request.service.config.appSettings.PhotoContainerName;
    accountName = request.service.config.appSettings.AccountName;
    accountKey = request.service.config.appSettings.AccountKey;
  // Connect to the Blob service.
  blobService = azure.createBlobService(
    accountName,accountKey,accountName + '.blob.core.windows.net');
  createContainer();
  createPolicies();
  var sasResponse = GetSAS();
  return request.respond(201, sasResponse);
}

应用程序密钥是一种与特定 Azure 移动服务通信的安全方法。服务将从门户获取此密钥,在 Raspberry Pi 上运行的代码也将使用此密钥,所以应将其放在手边。然后将其粘贴到客户代码中。客户接收封装在 URL 中的 SAS,以便在后续操作中用其上传照片。

现在我们来看看表示服务器端 Node.js 应用程序的一些代码。专门针对 Azure 的强大 Node.js SDK 可在 bit.ly/Unnikj 找到。此代码库为 Azure 平台的其他部分提供了诸多支持,如使用表格、队列、主题、通知中心和核心管理等。你可以在 bit.ly/1nBEvBa 找到有关集成 Node.js 和 Azure Blob 存储空间的教程。

我们的目的是将 Node.JS 应用程序返回给客户的 SAS 与之前配置的存储空间账户绑定在一起。同样地,Node.js 应用程序将需要存储空间账户的账户名称和管理密钥。

Azure 移动服务允许你外部化 Node.js 应用程序的配置设置。直接在代码中对账户密钥设置进行硬编码通常被认为是一个不好的编程惯例。应用设置允许你设置服务运行时能够读取的键-值对。你可以在 bit.ly/1pwGFRN 详细了解相关信息。

最后,Node.js 代码将返回包含 SAS(客户用于上传照片)的 URL。连同 SAS 令牌,Node.js 应用返回为 201 的 HTTP 状态。这意味着它已执行了请求并创建了新资源(SAS 令牌)。

有关 Node.js、SAS URL 和应用设置的更多详细演练步骤,可在 bit.ly/WyQird 中找到。

Raspberry Pi 代码内部

硬件/软件界面相当简单明了。你可以将客户端代码当作大型状态机。检查 GPIO 引脚的状态处于一个不断循环的模式中。从图 6 中可以看出,我们检查只是看看引脚是开还是关,正确还是错误。

图 6 轮询 Raspberry Pi GPIO 的循环接口以控制行为

while (true)
{
  // If pin 17 is off, we are ready.
  if (!s_Gpio.InputPin(FileGPIO.FileGPIO.enumPIN.gpio17))
  {
    showReady(true);
    // If pin 22 is true, indicate to the user that the system is busy,
    // take a photograph and then upload to the cloud.
    if (s_Gpio.InputPin(FileGPIO.FileGPIO.enumPIN.gpio22))
    {
      showReady(false);
      TakeAndSendPicture();
    }
  }
  else
  {
    // Not ready to take a photo yet.
    showReady(false);
    break;
  }
  // Pause for one-tenth of a second.
  System.Threading.Thread.Sleep(100);
}

有两种方法可以检查引脚状态。第一个方法是我们此处采用并将讨论的文件系统。通过检查特定值的特定文件,可以判断引脚是开还是关。另一个方法是检查内存中特定值的特定位置,此方法通常会提供更高的性能。尽管使用文件系统比较慢,但是更安全。此方法利用了 Linux 提供的内置驱动程序。

让我们使用 C# 解决在 Raspberry Pi 上的 Mono 上运行的代码。Raspberry Pi 将执行四个基本步骤。第一步,它将响应按门铃按钮的人。第二步,基于被按的门铃按钮,它将拍照并将其保存至本地存储空间。第三步,它将请求一个 SAS 令牌,稍后用于上传照片。第四步,它会将照片作为 blob 上传至 Azure 存储空间。尽管代码是用 C# 编写的,但全部都基于 REST。这意味着能够处理 HTTP 的任何语言或环境都可以执行这四步。

我们可以通过与 GPIO 进行通信来控制 Raspberry Pi。只有我们的应用程序将使用 GPIO 2 从中输入的和使用 GPIO 1 从中输出的四个点。我们将用一些 C# 代码连接这些引脚。请参见图 6 中的 Raspberry Pi C# 代码的主循环。引脚 17 和 22 用于测试设备准备情况和基于被按门铃按钮拍照及上传相片。

无限的轮询循环持续检查引脚的电压。所以软件知道需要完成哪些事情,如拍照。在轮询引脚状态时,此循环以 10hz 的频率运行。如果你对详细了解基准速度感兴趣,请转到 bit.ly/1trFzt9

不同的语言将以不同的速度运行。例如,Python 相当慢,因此无法以速度为首要条件的情况下使用。在这种情况下,对于当人按门铃时,检查门铃开关是否已上拉引脚 22 来供电来说,10hz 就足够快了。

三个引脚,每个引脚的功能不尽相同。第一个输入引脚 17 必须连接到接地引脚(被 C# 解读为错误)。如果不是按这种方式配置的,则程序会退出。引脚 22 必须设置为“供电”(被解读为正确)。当引脚 22 为正确时,拍照程序以 TakeAndSend­Picture 开始。showReady 方法使用引脚 4(输出引脚)显示程序已准备好通过驱动一个 LED 拍照并上传照片。

这个解决方案使用条件编译符号,与正常的 Windows/Desktop OS 进行对比,以便在 Raspbian OS 上运行合适的代码。解决方案中的代码必须不同,因为在 Raspbian OS 下拍照不是一个 API 调用。这将涉及在自己的流程中运行一个可执行程序。另一方面,在 Windows 8 中将使用 CameraCaptureUI 的 CaptureFileAsync API。

要拍照的话,我们将调用内置可执行的 raspistill 来完成,如 图 7 所示。它已被设置为进行快照并将其保存为 JPEG 格式。还可以指定文件名、照片质量和图像大小。Raspistill 将作为一个子进程启动。在拍照时,代码将请求 Azure 移动服务发送 SAS URL。然后将照片上传至 Azure Blob 存储空间。

图 7 拍照并将其上传至云

static void TakeAndSendPicture()
{
#if LINUX
  // Start photo-taking process. This will kick off the raspistill process.
  // We'll wait for it to exit after we get the photo URL.
  Process raspistill = new Process();
  raspistill.StartInfo = new ProcessStartInfo("/usr/bin/raspistill",
    "-n -q " + photoQuality +
    " -o /home/pi/Desktop/me.jpg -h 200 -w 200 -t 500")
    {
      UseShellExecute = false
    };
    raspistill.Start();
#endif
  // Get Photo URL while the picture is being taken.
  WebRequest photoRequest = WebRequest.Create(
    "https://raspberrypiservice.azure-mobile.net/api/getuploadblobsas?");
  photoRequest.Method = "GET";
  photoRequest.Headers.Add("X-ZUMO-APPLICATION", 
    "AIzNtpTdQLjORKJJhTrQWWRSHSnXcN78");
  PhotoResponse photoResp = null;
  using (var sbPhotoResponseStream = 
    photoRequest.GetResponse().GetResponseStream())
  {
    StreamReader sr = new StreamReader(sbPhotoResponseStream);
    string data = sr.ReadToEnd();
    photoResp = JsonConvert.DeserializeObject<PhotoResponse>(data);
  }
  Console.WriteLine("Pushing photo to SAS Url: " + photoResp.sasUrl);
  WebRequest putPhotoRequest = WebRequest.Create(photoResp.sasUrl);
  putPhotoRequest.Method = "PUT";
  putPhotoRequest.Headers.Add("x-ms-blob-type", "BlockBlob");
#if LINUX
  // Wait until the photo is taken.
  raspistill.WaitForExit();
  FileStream fs = new FileStream(@"/home/pi/Desktop/me.jpg", 
    FileMode.Open);
#else
  FileStream fs = new FileStream(@"testPhoto.jpg", FileMode.Open);
#endif
  using (fs)
  using (var reqStream = putPhotoRequest.GetRequestStream())
  {
    Console.WriteLine("Writing photo to blob...");
    fs.CopyTo(reqStream);
  }
  using (putPhotoRequest.GetResponse())
  {
  }
}

构建的 Visual Studio 解决方案可以在 Raspberry Pi 和传统的 Windows OS 上运行。此解决方案使用条件编译符号,与正常的 Windows Desktop OS 进行对比,以便在 Raspbian OS 上运行合适的代码。此解决方案中的代码必须不同,因为在 Raspbian OS 下拍照是特定于平台的。为了简单起见,在 Windows 8 中进行编译时,我们没有拍照。取而代之,我们将现有图像 (testPhoto.jpg) 上传至云。

值得高兴的是你可以在 Windows 和 Raspbian 之间共享大量的代码了。从获取 SAS 到上传照片的一切信息都是相同的。所以,如果在 Windows 中有效,很大程度上也会在 Raspbian 上有效。这极大地简化了基于客户端的代码开发和测试。Raspberry Pi 是一台受限设备,所以最好尽量减少在本台设备上完成的工作。

设备拍照之后,你可以在两个位置查看相片。第一个位置是设备本身,具体位置为 /home/pi/Desktop/me.jpg,在命令行上指定过此路径。当然,重点是然后要将图像上传至 Azure 存储空间账户容器。

总结

这是构建物联网设备的基本起点。我们尝试解决了从采购硬件、组装、安装软件、编写软件和测试功能这几方面涉及的所有问题。我们还提供了许多辅助性的博客文章。但是这个解决方案还不是完整的。

在接下来的文章中,我们将解决向手机发送推送通知的方法,以便在移动设备上查看按门铃人的照片。我们将介绍通知服务,它是 Azure 服务总线的一部分。我们还将构建能够从 Azure 接收推送通知的 Windows Phone 应用程序。最后,我们将解决其他存储机制,如使用 MongoDB NoSQL 数据库和连接人脸识别后端。敬请期待 Azure 物联网的更多内容。


Bruno Terkaly 是 Microsoft 的开发推广人员。他的知识深度来源于多年来相关领域以及使用大量平台、语言、框架、SDK、库和 API 编写代码的经验。他不辞辛苦,就有关构建基于云的应用程序(特别是使用 Microsoft Azure 平台)编写代码、发布博客并给予现场演示。您可以阅读他的博客 blogs.msdn.com/b/brunoterkaly

Steven Edouard 是 Microsoft 的开发推广者。在此之前,他在 .NET runtime 团队担任软件测试工程师,交付了 .NET Framework 4.5 和 .NET Native Compilation 等产品。现在,他满怀热情投入到这个让人们兴奋的领域,他通过参与技术演示、联机内容和演讲挖掘云计算服务的无限潜能。

衷心感谢以下 Microsoft 技术专家审阅本文内容:Gil Isaacs 和 Brent Stineman
在长达 20 多年的 IT 职业生涯中,Brent Stineman 接触过从移动设备到大型机的各个方面。目前,他专注于云计算和 Azure 平台,以在 Microsoft 开发人员体验 (DX) 部门同技术推广和开发 (TED) 小组一起帮助企业架构师成为技术的推广者。你可以通过以下方式关注 Brent:twitter.com/brentCodeMonkey

Gil Isaacs 在开发人员体验 (DX) 部门是一位 Microsoft Azure 平台的技术推广者。他的角色是帮助客户成功地快速成为基于云的架构师。Gil 的经验基于他多年的编码工作和帮助客户解决有关技术方面异类混合体的问题。Gil 目前的主要任务是将开源技术引入 Azure。你可以通过 twitter.com/gilisaacs 关注 Gil。