此文章由机器翻译。

Azure Insider

将 IoT 设备连接到云

Bruno Terkaly
Steven Edouard

Bruno Terkaly, Ricardo Villalobos 我们最近推出的物联网 (物联网) 设备连接的想法 — — 在我们的案例树莓派 — — 向云 (微软 Azure) 向您的移动设备提供推式通知,每当有人敲响你家的门铃。这让你看看谁在你大门一步在家从任何地方在世界使用您的移动设备。

在 2014 年 9 月的文章中,"汤对坚果:从原始硬件到 Cloud-Enabled 装置"(msdn.microsoft.com/magazine/dn781356),我们穿过进行集成的存储 blob 使用 Azure 移动服务来获取共享访问签名 (SAS) 设备的过程。这让设备直接将文件上载到没有任何服务层的存储容器。现在我们可以将照片上载到云,但仍有一些剩下要做的事情,要将通知发送到该设备。

到目前为止,我们利用 Azure 移动服务和 Azure 存储在此项目中。您还需要能够利用服务总线队列和计划的任务服务,以从树莓派设备向您的移动设备发送消息。这两个服务将结合 Azure 移动服务,从服务总线队列接收消息并将它们记录在数据库中。在开放源码和新时代的解决办法的精神,我们将使用 MongoDB 数据库主办,并由 MongoLab 您可以添加免费作为 Azure 的附加补充。

服务总线的分布式消息传递

在物联网计算方面,服务总线排队功能提供了强大的抽象。服务总线排队支持异步消息传递端点之间,并可以扩展以支持几乎任何工作负荷。它还允许应用程序相互通信云与房地上不需要虚拟联网可后勤和安全的噩梦。

我们解决这些问题在 2014 年 2 月列中,"Windows Azure 服务总线和物联网"(msdn.microsoft.com/magazine/dn574801)。Azure 提供另一种排队服务称为存储队列,其中有类似的功能,但在几个关键方面不同。我们选择去服务总线队列与发布服务器/订阅服务器功能、 更大的可扩展性,在消息处理和能够很容易地转换到其他服务的巴士服务。你可以阅读更多关于存储队列在 bit.ly/XSJB43

服务总线队列公开常规的基于 Rest 的 API,支持由长轮询接收消息的能力。这是一个技术的开放 HTTP 连接的一段时间。长轮询是一种大计算方案,因为它支持超时的物联网技术。这允许关闭直到下一个长轮询,纾缓电力消耗和网络资源的连接的设备。

我们会有 SmartDoor 门铃设备到单个移动服务多对一关系。更具体地说,这种形式的 pub/sub 将涉及许多消息出版商和只有单个邮件订阅服务器。订阅服务器在这种情况下将移动的服务,将从队列中读取。一个或多个 SmartDoor 设备将发布服务器。它是完全有可能做相反,以防你想要回向设备发送一条消息。

有四种类型的通信模式通常用于物联网方案。正如你可以看到在图 1,一个或多个树莓派设备要发布到服务总线队列的消息。蔚蓝的移动服务是服务总线队列只有订户。

建筑图的蔚蓝的移动服务从服务总线读取
图 1 建筑图的蔚蓝的移动服务从服务总线读取

蔚蓝的移动服务提供综合的任务调度器,可以在固定的时间间隔安排任务。我们会在 60 秒的间隔从服务总线队列读取您的问题。检查我们的博客文章在 bit.ly/1o1nUrY 为更详细的信息连接到服务总线队列的移动服务。我们预定的任务,doorBellListener (见图 2),我们会从队列中读取通过服务总线 API Node.js Azure SDK 中的使用我们的连接字符串。

图 2 Azure 移动计划任务 doorbellListener

// Get a reference to the azure module
var azure = require('azure');
// Get our service bus connection string
var connectionString = process.env.ServiceBusConnectionString;
// This task will run for 60 seconds so the initial timeout should be 60
var c_Timeout = 60;
function doorbellListener() {
  //Get the current unix time in seconds
  var date = new Date();
  var time = date.getTime();
  var startSeconds = time / 1000;
  var sb = azure.createServiceBusService(connectionString);
  listenForMessages(c_Timeout);
  // Define a function that will listen for messages
  // on the queue for number of seconds
  function listenForMessages(seconds) {
    console.log('Doorbell Listener Started for timeout: ' + seconds);
    // Long poll the service bus for seconds
    sb.receiveQueueMessage("smartdoor", { timeoutIntervalInS: seconds },
      function(err, data) {
        if(err){
          // This path will get hit if we didn't get any messages
          console.log(err);
        }
        else{
          // We have received a message from a device
          var ringNotification = JSON.parse(data.body);
          console.log('recieved message from doorbell ' +
            ringNotification.doorbellId + '
            with image link ' + ringNotification.imagePointer)
          function continueListeningForMessages(){
            // Go back and listen for more messages for the duration of this task
            var currentDate = new Date();
            var currentSeconds = currentDate.getTime() / 1000;
          console.log('currentSeconds ' + currentSeconds);
          console.log('startSeconds ' + startSeconds);
        // Compute the seconds between when we started this scheduled task and now
        // This is the time that we will long-poll the service bus
        var newTimeout = Math.round((c_Timeout - (currentSeconds - startSeconds)));
          if(newTimeout > 0){
          // Note: the receiveQueueMessage function takes ints no decimals!!
          listenForMessages(newTimeout);
        }
      }

由于 Node.js 的异步特性和行为的将一条消息发送到服务总线通过长轮询,您必须计算传递给 recieveQueueMessage,取决于如何此实例的已计划的任务已运行了长时间的超时值。这将阻止任务的多个实例同时运行。

服务总线的好在于它暴露出基于 Rest 的 API。不管你使用什么设备,你可以向它发送消息。Azure 主要语言 (如 Python、 Ruby、 Node.js 和 C# 具有 Sdk。因为我们想要的代码,以转换为任何平台,我们将直接使用基于 Rest 的 API。

要将发送一条消息,我们需要为服务总线队列创建一个策略。这项政策是允许在服务总线上的某些操作的关键。 有一定数量的政策,任何给定的队列。考虑到你自己的问题域中的帐户。

我们会生成名为 DevicePolicy,只让用户将消息发送到服务总线队列策略 (请参阅图 3)。

这将确保关键过获取落入恶人手中,没有人可以听服务总线上的消息。

DevicePolicy 会将消息发送到服务总线队列
图 3 DevicePolicy 会将消息发送到服务总线队列

树莓派来发送消息的服务总线队列的设备代码所示图 4

图 4 发送一条消息,指示张成功的照片上传

Console.WriteLine("Sending notification to service bus queue");
  WebRequest sbRequest = WebRequest.Create(
    "https://smartdoordemo.servicebus.Windows.net/smartdoorqueue/messages");
  var headers = sbRequest.Headers;
  sbRequest.Method = "POST";
  using (var sbMessageStream = sbRequest.GetRequestStream())
  {
    string body = JsonConvert.SerializeObject(new DoorBellNotification()
    {
      doorBellID = deviceID,
      imageUrl = photoResp.photoId
    });
    var bytes = Encoding.UTF8.GetBytes(body);
    sbMessageStream.Write(bytes, 0, bytes.Length);
    headers.Add("Authorization", createToken(
      "https://smartdoordemo.servicebus.Windows.net/smartdoorqueue/
      messages", "DevicePolicy",
      ConfigurationManager.AppSettings["ServiceBusSharedAccessKey"]));
  }
  try
  {
    Console.WriteLine("Sending door bell notification for " + deviceID);
    using (var response = sbRequest.GetResponse())
    {
      Console.WriteLine("Sucessfully Sent");
    }
  }
  catch (Exception e)
  {
    Console.WriteLine("Couldn't post to service bus -" + e);
  }

你可以看到完整的代码在 program.cs bit.ly/1qFYAF2。在此代码中,我们做服务总线队列基于 Rest 的 API 上的 POST 请求,并提供 SAS,类似于我们收到移动服务为 blob 存储的签名。此 SAS 被由使用 sha-256 算法的加密密钥。它定义了 SAS 过期时间以及正在访问的资源。CreateToken 方法是简单的方法,构建了基于您共享访问键从 DevicePolicy 策略的 SAS。

后构建的 SAS,我们将其放在 HTTP 标头,并将序列化的消息 (以 JSON 格式) 放在请求正文。POST 请求后,一条消息发送到服务总线队列以指示成功上的传一张照片。该消息包含了指向上载的 blob 和这个门铃设备来自于应用程序设置的唯一标识符。此标识符可以找到在 app.config xml 文件中,坐在旁边的门铃监听器可执行:

<appSettings>
  <add key="DoorBellID" value="123456"/>
...
</appSettings>

现在的设备当它运行时发送消息发布到服务总线。去我们的移动服务着陆页日志选项卡可以访问现场服务日志输出。当我们运行的 C# 代码 Visual Studio,你可以看到移动服务门户中得到通过日志输出消息的内容。

虽然 Azure 移动服务为存储提供了很好的表 (由 SQL Server 支持),我们将略有不同的做法。我们会把 MongoDB 用作我们的数据库解决方案。MongoDB 与 Node.js 上性能很好,因为它是一个文档 — —­面向数据库和类似于存储的 JSON 对象的集合。阅读更多关于 MongoDB 开放源码项目在 mongodb.org。 我们将使用 MongoLab,作为一个提供服务 (DaaS),一个数据库来承载我们的数据库。

我们需要的数据库来跟踪一对夫妇的事情:

  • 门铃 — — 一种个别的树莓派设备
  • 图片 — — 个人的图片所采取的门铃

一旦 MongoDB 数据库设置,我们可以使用猫鼬,如我们 MongoDB 的司机为我们 Azure 移动服务 Node.js 代码通过安装到我们的手机服务 Git 存储库。这是我们经历的 qs 节点将模块安装到相同的过程:

npm install mongoose

推动本地资源库中会触发要安装到其存储库中的猫鼬的移动服务。这是我们可以进入任何 Node.js 包 Azure 移动服务。随着每个 Node.js 模块,我们可以引用它使用 RequireJs 并使用我们 MongoDB 的连接字符串初始化猫鼬,在我们计划的任务:

var mongoose = require('mongoose');

猫鼬将负责在我们的文档数据库中创建一个结构化的模式 (请参阅图 5)。我们将使用两个实体:门铃和照片。在我们的数据库中的每个门铃对象将代表一个覆盆子 Pi 设备。它将包含一个唯一的标识符,doorBellID 和照片对象的数组。每张照片包含一个链接到 blob 存储作为服务所生成的当它接收到一个服务总线消息的时间戳的照片。

图 5 架构定义在猫鼬

var photoSchema = mongoose.Schema({
                url : String,
                timestamp: String
            });
var doorbellSchema = mongoose.Schema({
            doorBellID: String,
            photos: [photoSchema]
        });
var Photo = mongoose.model('Photo', photoSchema)
var DoorBell = mongoose.model('DoorBell', doorbellSchema);
// Expose these schemas
exports.DoorBell = DoorBell;
exports.Photo = Photo;

我们公开公开通过出口关键字的门铃和照片的架构。请注意如何 photoSchema 交叉存取在 doorbellSchema 中。这是存储在数据库中时,如何将反映数据。

我们会将此代码放我们移动服务存储库中的共享文件夹中。这让我们在我们的服务在任何地方使用的架构。若要引用的架构中我们预定的任务,我们只需要将其导入与需要声明:

var schemas = require('../shared/schemas.js');

现在我们可以在我们预定的任务中使用这些架构来定义新的数据库实体。我们将使用专门的函数以确保我们正在连接到 MongoDB 和执行回调。收到一条消息从服务总线,移动服务应:

  1. 检查数据库中是否存在与消息中指定的 doorBellID 门铃。
  2. 如果它不存在,创建一个新的门铃实体与这张照片。
  3. 如果它不存在,只是将新照片追加到现有门铃系列照片。

你可以在代码中为此看见逻辑图 6

图 6 逻辑验证门铃和管理图片

// Create database entry for this image
dbConnectAndExecute(function(err){
  if(err){
    console.log('could not record image to database -' + err);
    return;
  }
  DoorBell.findOne({ doorBellID: ringNotification.doorBellID},
    function(err, doorbell){
    if(err){
      return console.error(err);
    }
    if(doorbell === null){
      console.log('Doorbell not found in DB, creating a new one');
      // Take the entire body's json, assuming it fits into this schema
      var entityObject = {
        doorBellID : ringNotification.doorBellID,
        photos : []
      };
      entityObject.photos.push({
        url : ringNotification.imageUrl,
        // Set timestamp to current server time
        timestamp : ((new Date()).getTime()).toString()
      })
      var doorbell = new DoorBell(entityObject);
    }
    else{
      // We already have this device in the database—add a picture
      doorbell.photos.push({
        url : ringNotification.imageUrl,
        // Set timestamp to current server time
        timestamp : ((new Date()).getTime()).toString()
      });
    }
    // Commit changes to db
    doorbell.save(function (err, entity) {
    if(err){
      return console.error(err);
    }
    console.log('sucessfully created new entity for: ' + entity);
    return;
  });
  });
});

你可以看到一个完整演示的 doorbelllistener 在 bit.ly/VKrlYU

我们调用 dbConnectAndExecute 函数,以确保我们对我们在 MongoLabs 的 MongoDB 数据库联系在一起。然后我们与消息中指定的 ID 查询数据库中的门铃。如果查询是空的我们创建一个新的门铃实体。否则为我们将照片附加到的牵强的实体,并将更改提交到数据库。

现在看到在图 7 当我们与我们的覆盆子 Pi 设备发送照片和服务总线的消息时,会发生什么。

发送的照片和消息的结果
图 7 发送的照片和消息的结果

检查在门户中的移动服务日志,树莓派已发送消息显示这张照片后被处理并添加到数据库中。

对于良好的措施,我们可以检查我们 MongoDB 数据库,以确保我们实际上正在跟踪的照片。MongoLab 提供了一个丰富的 MongoDB 检查界面,通过其门户网站。图 8 显示什么门铃实体可能看起来像一对夫妇的图片上传后。

MongoDB 注册条目
图 8 MongoDB 注册条目

现在,树莓派完全集成到我们的云服务。该设备可以上载文件,直接以云存储、 通知云服务通过服务总线和已存储在数据库中的信息。

本系列的下一步将结合推式通知的图像移动到云后端应用程序。我们将使用自定义的 Api 来地面物体从数据库到应用程序和集成图像人脸识别一个第三方的 API,并通知枢纽电源推式通知。你可以检查代码存储库来构建及调整自己的解决方案。Azure 移动服务的后端是在 bit.ly/1mHCl0q。你会发现在覆盆子 Pi 代码 bit.ly/1l8aOtF

集成树莓派设备到云后端是至关重要的。利用开放源码技术如 Node.js 道理的在许多情况下,你在哪里使用一个轻量级的后端来扩展到大量的设备。

Azure 服务总线提供了安全、 方便的方法,偶尔连接的设备,能够利用一个可靠的消息传递基础架构,可以扩展到多个客户端。最后,利用 NoSQL 商店是一种流行的方式来保持数据和保留本机 JavaScript 语法中的数据层和后端 Node.js 服务。


Steven Edouard 是在微软开发者福音传教士。在此之前,他担任在.NET 运行时团队提供产品,如.NET 框架 4.5 和.NET 本地编译的软件测试工程师。现在他的激情所在令人兴奋的人物,对云计算服务,通过技术演示、 在线内容和演示文稿的无限潜力。

Bruno Terkaly 是 Microsoft 的开发推广人员。他的知识深度来源于多年来相关领域以及使用大量平台、语言、框架、SDK、库和 API 编写代码的经验。他花时间写代码、 博客和直播演讲上构建基于云的应用程序,具体地使用微软 Azure 平台。您可以阅读他的博客 blogs.msdn.com/b/brunoterkaly

衷心感谢以下 Microsoft 技术专家对本文的审阅:吉尔 · 艾萨克斯和布伦特 Stineman