互联网上的东西

服务总线上的智能恒温

Clemens Vasters

下载代码示例

这里是一个大胆的预测:连接的设备都将大生意,和理解这些设备将为开发人员不太远了这条路非常重要。 "很明显,"你说。 但我不是说,你可能会读这篇文章的设备。 我的意思是那些会让你很酷,今年夏天,可帮助您洗衣服和盘子,那冲泡咖啡或工厂地板上放在一起的其他设备。

在 6 月发行的 MSDN 杂志 (msdn.microsoft.com/magazine/jj133825),解释了一系列的因素,并概述了如何管理事件和命令流动,从和嵌入式 (和移动) 设备使用 Windows Azure 服务总线体系结构。 在本文中,我借东西要还迈出的一步,看看代码,创建并保护这些事件和命令的流动。 嵌入式设备真正了解确实需要看看,因为我会生成一个然后钢丝它到 Windows Azure 服务总线因此它可以发送到其当前状态与相关事件,并将远程控制的消息 (通过 Windows Azure 云。

直到几年前,建设与电源、 微控制器和一套传感器的一种小型设备需要相当多的电子硬件设计中以及在放到一起,更不用提了烙铁熟练掌握的技能。 我高兴地承认我已经亲自已相当挑战硬件部门 — — 这么多的这样的一个朋友曾宣称如果世界遭到外星人的机器人,他会把我送到前线,我只是在场会导致崩溃于电气短裤盛大烟花的攻击。 但由于样机平台如 Arduino/Netduino 或.net Gadgeteer 的兴起,甚至可能会做伤害人和机器摆动烙铁可以现在一起提出一个功能齐全的小型设备,利用现有的编程技巧的人。

要坚持建立中的最后一个问题的方案,我会建造"空调"形式的恒温控制的风机、 风扇在哪里布线的角度最有趣的部分。 该项目的组件基于.net Gadgeteer 模型中,涉及与微控制器、 内存和各种可插拔模块的主板。 为项目主板是下面的扩展模块地质电子非斯蜘蛛板:

  • 从地质电子
    • 以太网 J11D 模块提供有线的网络 (wi-fi 无线上网模块存在)
    • USB 客户端 DP 模块作为电源和 USB 端口进行部署
    • 在设备的直接控制的游戏杆
  • 从熔融工作室
    • 温度和湿度传感器
    • 若要打开或关闭切换风扇的继电器
    • OLED 显示,以显示当前状态

在一起,这些部件的成本约 230 元。 这是显然比焊一块板,相当的组件,但提到这需要焊接吗? 此外,这是一个刚刚开始走吧,所以预期价格涨到了这个基地扩大的市场。

若要使组件来活着您所需要的 Visual C# 2010年表示 (至少),.net 微框架 SDK 和从地质电子或熔融 Gadgeteer SDK。 一旦你有了这些安装,发展经验是 — — 如果您会允许比较级 — — 相当壮观和为视觉,在 Visual Studio 中,想象可以得到的东西,正如您看到的图 1

Designing the Device in the .NET Gadgeteer
图 1 设计.net Gadgeteer 中的设备

图 1 .NET Gadgeteer 程序的设计视图显示在 Visual Studio 中。 我认为这篇文章,与实际的设备的一张照片,包括但照片会做的就是确认该关系图。 这正是它的外观。

具有.gadgeteer 扩展名的文件包含 XML 模式,可视化编辑器中。 从该 XML 文件,模具的 Gadgeteer 自动生成封套的部分程序类的每个模块插入到主板。 您的代码坐在 program.cs,然后从持有的程序类,就像你熟悉从其他.net Api 的隐藏模型的另一部分。

这些设备的情况下使用.net 微框架。 它是完全开放源代码版本的 Microsoft.net 框架已专门创建于小型设备与有限的计算能力和不多的内存。 .NET 微框架包含许多熟悉的.net 框架类,但大部分已经通过特色饮食以减少整体代码足迹。 因为框架是一个图层上本机硬件的设备和设备并不是处理 (那里真的是这里没有 OS) 的所有硬件抽象的 OS 的通用计算机,您可以使用设备的框架版本取决于支持的前提条件,这显然非常不同于常规的 pc 体验的主板制造商凡远从东西作为.net 框架为高级别上删除硬件的特殊性。

还有几个其他差异与常规的.net Framework 中和一般的 PC 平台相比,— — 来自 PC 背景 — — 最初令人惊讶。 例如,该设备在这里没有一个板载电池。 没有电池意味着无缓冲的时钟,因此该设备具有正确的挂钟时间它醒来的时候不知道。 缺乏 OS 中,使用第三方扩展显示,该设备也不会有板载的字体,您可以使用绘制显示的字符串。 如果您想要显示的字符串,你得要添加的字体来这样做。

同样,该设备没有 prepopulated、 Windows 更新维护证书存储区。 如果您想要验证 SSL/TLS 证书,你必须至少部署到设备的根 CA 的证书 — — 当然你还得有当前时间检查证书的有效性。 正如你可能已经猜到,证书的处理表示有点障碍对于这些设备,和 SSL/TLS 加密要求计算的努力、 内存消耗和代码并不是所有的设备可以支持它们的占地面积太大。 然而,因为安全显然变得越来越重要,即使在这个空间设备需要跨互联网、 通信微.net 4.2 版带来重大 SSL/TLS 的改进支持设备有足够的资源来处理它。 我将讨论这个问题有点后来更深入。

恒温功能

实施此示例的本地调温功能是相当简单的。 我检查温度和湿度对计划使用传感器和交换机通过的中继端口关闭或打开一个连接,当温度低于或高于某一阈值时,风扇。 OLED 屏幕上显示的当前状态和操纵杆允许手动调整目标温度。

当在开始该设备时,我就会事件丝计时器触发温度读数,并从操纵杆读取事件。 当按下操纵杆时,我暂停计时器、 检查目标温度操纵杆位置、 立即请求新的温度从传感器读数和恢复计时器。 温度读数结束时,TemperatureHumidity­MeasurementComplete 事件获取提出的传感器。 我然后存储当前的读数和调整中继切换风扇,必要时的状态。 这就是恒温的逻辑,而会显示部分的程度图 2

图 2 阅读温度和湿度

void WireEvents()
{
  this.InitializeTemperatureSensor();
  this.InitializeJoystick();
}
void InitializeTemperatureSensor()
{
  this.temperatureCheckTimer = new Timer(5000);
  this.temperatureCheckTimer.Tick += (t) =>
    this.temperatureHumidity.RequestMeasurement();
  this.temperatureCheckTimer.Start();
    this.temperatureHumidity.MeasurementComplete 
    += this.TemperatureHumidityMeasurementComplete;
}
void InitializeJoystick()
{
  this.joystick.JoystickPressed += this.JoystickPressed;
}
void JoystickPressed(Joystick sender, Joystick.JoystickState state)
{
  this.temperatureCheckTimer.Stop();
  var jStick = this.joystick.GetJoystickPostion();
  if (jStick.Y < .3 || jStick.X < .3)
  {
    settings.TargetTemperature -= .5;
    StoreSettings(settings);
  }
  else if (jStick.Y > .7 || jStick.X > .7)
  {
    settings.TargetTemperature += .5;
    StoreSettings(settings);
  }
  this.RedrawDisplay();
  this.temperatureHumidity.RequestMeasurement();
  this.temperatureCheckTimer.Start();
}
void TemperatureHumidityMeasurementComplete(TemperatureHumidity sender, 
  double temperature, double relativeHumidity)
{
  var targetTemp = settings.TargetTemperature;
  this.lastTemperatureReading = temperature;
  this.lastHumidityReading = relativeHumidity;
  this.relays.Relay1 = (lastTemperatureReading > targetTemp);
  this.RedrawDisplay();
}

每当我调整目标温度在 JoystickPressed 方法中的,我在程序类的设置字段中存储的新值,并调用 StoreSettings。 设置字段是 ApplicationSettings 的类型,可序列化的类所拥有的一切设备的设备代码中需要记住整个重置和电源周期。 要坚持存储数据,.net 微储备设备的非易失性内存中的某些存储页,并提供对此存储通过 ExtendedWeakReference 类的访问。 这可能不是直观直到你承认它是主要是一种机制来交换的数据从主内存的压力下,它方便可兼作存储功能。 该类拥有弱引用的对象,就像定期 WeakReference 在.net Framework 中,但将交换数据的非易失性存储而不是放弃它,垃圾回收器一旦。 因为数据获取换出主内存,它需要被序列化存储,这解释了为什么 ApplicationSettings 类 (您将看到用于以后当我们讨论资源调配) 需要是可序列化的。

恢复对象从其存储位置或使用 RecoverOrCreate 方法创建一个新的存储插槽时,需要指定一个唯一的标识符。 我只有一个对象来存储,因此,我将使用一个固定的标识符 (零)。 存储该对象并将其恢复一旦被迫回到存储在 ExtendedWeakReference 实例上使用 PushBackIntoRecoveryList 方法所需要的任何更新后,这就是我做什么 StoreSettings 冲出来,变化中所示图 3

图 3 更新存储的数据

static ApplicationSettings GetSettings()
{
  var data = ExtendedWeakReference.RecoverOrCreate(
    typeof(ApplicationSettings),
    0,
    ExtendedWeakReference.c_SurviveBoot | 
    ExtendedWeakReference.c_SurvivePowerdown);
  var settings = data.Target as ApplicationSettings;
  if (settings == null)
  {
    data.Target = settings = ApplicationSettings.Defaults;
  }
  return settings;
}
static void StoreSettings(ApplicationSettings settings)
{
  var data = ExtendedWeakReference.RecoverOrCreate(
    typeof(ApplicationSettings),
    0,
    ExtendedWeakReference.c_SurviveBoot | 
    ExtendedWeakReference.c_SurvivePowerdown);
  data.Target = settings;
  data.PushBackIntoRecoverList();
}

配置

一开始,该设备处于"工厂新"状态 — — 已部署的设备代码但设备尚未未初始化,因此不会有任何的当前设置。 你可以看到这种状态时设置对象仍为空,因此已初始化为默认设置,在 GetSettings 方法中反映出来。

因为我想让沟通并通过互联网基础设施的设备 — — Windows Azure 服务总线 — — 我需要配备一组凭据以交谈的基础设施,还告诉它跟哪些资源的设备。 这第一步设置厂新设备所需的网络配置和设置匹配的资源,在服务器端的被称为资源调配 ; 在上一篇文章中,我为它讨论的基本体系结构模型。

设备的代码中,我将会得到妥善调配的设备相当严格,将启动资源调配的步骤,每次在设备连接到网络时,并没有一个有效的配置。 为此,我把一个布尔值,在设置来告诉我是否过上一个成功的标志。 如果未设置标志,我发出对 Windows Azure 中承载的资源调配服务的调用。

负责核查时它产生由服务维护标识的使用其唯一的设备标识符,在允许列表中注册该设备的资源调配服务。 一旦激活设备,它获取从允许列表中删除。 要保持相当简单的这篇文章的事情,不过,我跳过允许列表管理的实施。

一旦设备被认为是合法的资源调配服务,之后在上一篇文章中,建立的模型分配到特定的刻度单位和该刻度单位内的特定的扇出主题的设备。 对于此示例,我要保持简单,并创建订阅,为单个固定主题命名为充当从云变成该设备,命令通道的设备和命名事件要从设备收集的信息的主题。 除了创建订阅和将该设备与该主题相关联,我还在访问控制服务 (Windows Azure Active directory 功能) 中创建的设备的服务标识和授予该标识所需的权限,将消息发送到事件主题,主题的设备从新创建的订阅接收消息。 该设备可以执行正是这两个操作 Windows Azure 服务总线上的 — — 而已。

图 4 显示的资源调配服务的核心。 该服务依赖于 Windows Azure 服务总线管理 API (NamespaceManager) 在核心船舶作为一部分的 Windows Azure sdk 或通过 NuGet 的 Microsoft.ServiceBus.dll 大会中找到。 它还依赖于一个用于管理访问控制帐户的佣工库和权限可作为零件及服务总线的授权采样,当然,也包括在这篇文章的下载代码。

图 4 的资源调配服务

namespace BackendWebRole
{
  using System;
  using System.Configuration;
  using System.Linq;
  using System.Net;
  using System.ServiceModel;
  using System.ServiceModel.Web;
  using Microsoft.ServiceBus;
  using Microsoft.ServiceBus.AccessControlExtensions;
  using Microsoft.ServiceBus.Messaging;
  [ServiceContract(Namespace = "")]
  public class ProvisioningService
  {
    const string DevicesTopicPath = "devices";
    const string EventsTopicPath = "events";
    static readonly AccessControlSettings AccessControlSettings;
    static readonly string ManagementKey;
    static readonly string NamespaceName;
    static Random rnd = new Random();
      static ProvisioningService()
      {
        NamespaceName = ConfigurationManager.AppSettings["serviceBusNamespace"];
        ManagementKey = ConfigurationManager.AppSettings["managementKey"];
        AccessControlSettings = new AccessControlSettings(
          NamespaceName, ManagementKey);
      }
      [OperationContract, WebInvoke(Method = "POST", UriTemplate = "/setup")]
      public void SetupDevice()
      {
        var rcx = WebOperationContext.Current.OutgoingResponse;
        var qcx = WebOperationContext.Current.IncomingRequest;
        var id = qcx.Headers["P-DeviceId"];
        if (this.CheckAllowList(id))
        {
          try
          {
            var deviceConfig = new DeviceConfig();
            CreateServiceIdentity(ref deviceConfig);
            CreateAndSecureEntities(ref deviceConfig);
            rcx.Headers["P-DeviceAccount"] = deviceConfig.DeviceAccount;
            rcx.Headers["P-DeviceKey"] = deviceConfig.DeviceKey;
            rcx.Headers["P-DeviceSubscriptionUri"] =
              deviceConfig.DeviceSubscriptionUri;
            rcx.Headers["P-EventSubmissionUri"] = deviceConfig.EventSubmissionUri;
            rcx.StatusCode = HttpStatusCode.OK;
            rcx.SuppressEntityBody = true;
          }
          catch (Exception)
          {
            rcx.StatusCode = HttpStatusCode.InternalServerError;
            rcx.SuppressEntityBody = true;
          }
        }
        else
        {
          rcx.StatusCode = HttpStatusCode.Forbidden;
          rcx.SuppressEntityBody = true;
        }
      }
      static void CreateAndSecureEntities(ref DeviceConfig deviceConfig)
      {
        var namespaceUri = ServiceBusEnvironment.CreateServiceUri(
          Uri.UriSchemeHttps, NamespaceName, string.Empty);
        var nsMgr = new NamespaceManager(namespaceUri,
          TokenProvider.CreateSharedSecretTokenProvider("owner", ManagementKey));
        var ruleDescription = new SqlFilter(
          string.Format("DeviceId='{0}' OR Broadcast=true",
            deviceConfig.DeviceAccount));
        var subscription = nsMgr.CreateSubscription(
          DevicesTopicPath, deviceConfig.DeviceAccount, ruleDescription);
        deviceConfig.EventSubmissionUri = new Uri(
          namespaceUri, EventsTopicPath).AbsoluteUri;
        deviceConfig.DeviceSubscriptionUri =
          new Uri(namespaceUri,
            SubscriptionClient.FormatSubscriptionPath(
              subscription.TopicPath,
              subscription.Name)).AbsoluteUri;
        GrantSendOnEventTopic(deviceConfig);
        GrantListenOnDeviceSubscription(deviceConfig);
      }
      static void GrantSendOnEventTopic(DeviceConfig deviceConfig)
      {
        var settings = new AccessControlSettings(NamespaceName, ManagementKey);
        var topicUri = ServiceBusEnvironment.CreateServiceUri(
          Uri.UriSchemeHttp, NamespaceName, EventsTopicPath);
        var list = NamespaceAccessControl.GetAccessControlList(topicUri, settings);
        var identityReference =
          IdentityReference.CreateServiceIdentityReference(
            deviceConfig.DeviceAccount);
        var existing = list.FirstOrDefault((r) =>
          r.Condition.Equals(identityReference) &&
          r.Right.Equals(ServiceBusRight.Send));
        if (existing == null)
        {
          list.AddRule(identityReference, ServiceBusRight.Send);
          list.SaveChanges();
        }
      }
      static void GrantListenOnDeviceSubscription(DeviceConfig deviceConfig)
      {
        var settings = new AccessControlSettings(NamespaceName, ManagementKey);
        var subscriptionUri = ServiceBusEnvironment.CreateServiceUri(
          Uri.UriSchemeHttp,
          NamespaceName,
          SubscriptionClient.FormatSubscriptionPath(
            DevicesTopicPath, deviceConfig.DeviceAccount));
        var list = NamespaceAccessControl.GetAccessControlList(
          subscriptionUri, settings);
        var identityReference = IdentityReference.CreateServiceIdentityReference(
          deviceConfig.DeviceAccount);
        var existing = list.FirstOrDefault((r) =>
          r.Condition.Equals(identityReference) &&
          r.Right.Equals(ServiceBusRight.Listen));
        if (existing == null)
        {
          list.AddRule(identityReference, ServiceBusRight.Listen);
          list.SaveChanges();
        }
      }
      static void CreateServiceIdentity(ref DeviceConfig deviceConfig)
      {
        var name = Guid.NewGuid().ToString("N");
        var identity =
          AccessControlServiceIdentity.Create(AccessControlSettings, name);
        identity.Save();
        deviceConfig.DeviceAccount = identity.Name;
        deviceConfig.DeviceKey = identity.GetKeyAsBase64();
      }
        bool CheckAllowList(string id)
      {
        return true;
      }
  }
}

该服务包括单个 HTTP 资源,名为 /setup,使用 Windows 通信基础 (WCF) Web 操作 SetupDevice,它接受的 POST 请求来实现。 您会注意到方法是无参数,也不会返回实体有效载荷。 这就是无事故。 而不是使用 HTTP 实体主体在 XML、 JSON 或窗体编码进行请求和响应的信息,我制作非常简单的设备,将有效载荷放在自定义 HTTP 标头。 这消除了需要特定的分析器分析有效载荷,并使代码占用空间小。 HTTP 客户端已经知道如何分析标头,并为我想在这里做的这是很多。

调用的 HTTP 资源的匹配设备代码所示图 5,很难想象调用任何比这更简单的制作。 设备标识符发送标头中并通过接头同样返回的 post-provisioning 的配置设置。 没有戏法流,没有解析,只是简单的键/值对 HTTP 客户端很容易理解。

图 5 配置设备

bool PerformProvisioning()
{
  [ ...
display status ...
]
  try
  {
    var wr = WebRequest.Create(
      "http://cvdevices.cloudapp.
net/Provisioning.svc/setup");
    wr.Method = "POST";
    wr.ContentLength = 0;
    wr.Headers.Add("P-DeviceId", this.deviceId);
    using (var wq = (HttpWebResponse)wr.GetResponse())
    {
      if (wq.StatusCode == HttpStatusCode.OK)
      {
        settings.DeviceAccount = wq.Headers["P-DeviceAccount"];
        settings.DeviceKey = wq.Headers["P-DeviceKey"];
        settings.DeviceSubscriptionUri = new Uri(
          wq.Headers["P-DeviceSubscriptionUri"]);
        settings.EventSubmissionUri = new Uri(
          wq.Headers["P-EventSubmissionUri"]);
        settings.NetworkProvisioningCompleted = true;
        StoreSettings(settings);
        return true;
      }
    }
  }
  catch (Exception e)
  {
    return false;
  }
  return false;
}
void NetworkAvailable(Module.NetworkModule sender,
  Module.NetworkModule.NetworkState state)
{
  ConvertBase64.ToBase64String(ethernet.NetworkSettings.PhysicalAddress);
  if (state == Module.NetworkModule.NetworkState.Up)
  {
    try
    {
      Utility.SetLocalTime(NtpClient.GetNetworkTime());
    }
    catch
    {
      // Swallow any timer exceptions
    }
    if (!settings.NetworkProvisioningCompleted)
    {
      if (!this.PerformProvisioning())
      {
        return;
      }
    }
    if (settings.NetworkProvisioningCompleted)
    {
      this.tokenProvider = new TokenProvider(
        settings.DeviceAccount, settings.DeviceKey);
      this.messagingClient = new MessagingClient(
        settings.EventSubmissionUri, tokenProvider);
    }
  }
}

如果设置指示资源调配是必要的执行­从它的 NetworkAvailable 的功能,当网络是否正常,该设备指派一个 IP 地址,通过 DHCP 获取触发资源调配方法被调用。 资源调配完成后,设置用于配置的令牌提供程序和邮件客户端,谈到 Windows Azure 服务总线。 您还会注意 NTP 客户端调用。 NTP 主张"网络时间协议",并简单、 BSD 许可证 NTP 客户写的迈克尔 · 施瓦茨,使该示例获取当前时间,如果您想要检查 SSL 证书的过期日期,则需要借来。

正如您看到的回图 4,SetupDevices 在允许列表 CheckAllowList 调用模拟检查,并且,如果这就是成功的然后调用 CreateServiceIdentity 和 CreateAndSecureEntities。 CreateServiceIdentity 方法在与为应用程序配置的 Windows Azure 服务总线命名空间关联的访问控制命名空间中创建一个新的服务标识,以及一个密钥。 CreateAndSecureEntities 方法通过包括具有布尔值 true 的广播属性创建新的订阅该主题,使用 SQL 的规则,允许将消息发送到要通过包括 DeviceId 属性设置为该设备的帐户名称,目标或特定订阅的主题配置订阅的设备上的实体或所有订阅。 已创建订阅后,该方法调用的授予所需的权限,对使用访问控制库的新服务标识实体的 GrantSendOnEventTopic 和 GrantListenOnDeviceSubscription 的方法。

一旦所有的已成功完成,资源调配操作的结果是映射到该请求的 HTTP 响应中的标头和 OK 状态代码,返回和设备将结果存储在非易失性内存中 NetworkProvisioningCompleted 设置的标志。

将事件发送和接收的命令

资源调配完成,现在准备将事件发送到 Windows Azure 服务总线事件主题并接收命令从其订阅主题的设备装置。 但我到那里去之前,我要讨论的一个敏感问题:安全性。

正如我刚才所说,SSL/TLS 是适用于小型设备昂贵协议套件。 即是说,某些设备不会永远能够支持 SSL/TLS,或者他们可能由于计算能力或内存限制支持只在有限的时尚。 实际上,虽然在写这篇文章的时候地质电子非斯蜘蛛主板基于在这里使用的.net 微框架 4.1 名义上可以说话 SSL/TLS,因此 HTTPS,其 SSL/TLS 固件显然不能处理向它提交的 Windows Azure 服务总线或访问控制服务的证书链。 随着这些设备的固件获取更新到新 4.2 版本的.net 微框架,这些限制将消失为此特定的设备,但有些设备是简单的问题太约束处理 SSL/TLS 仍然是真实的原则,并且有嵌入式的设备社会上不是很像重量级的相应协议选择积极的讨论。

因此,即使设备现在有一个适当的帐户,它不能得到一个令牌从访问控制服务因为使用 HTTPS 是这样做的一个先决条件。 同样是如此入 Windows Azure 服务总线,对于要求传递一个访问令牌包括使用队列和主题的所有交互操作的所有请求的任务 HTTPS 发送一条消息。 此外,如果此示例是生产代码,我想,当然,要公开通过 HTTPS 来保护密钥,它将返回到该设备的资源调配的终结点。

现在怎么样? 好吧,"现在"是最终作出适当权衡,这肯定包括钱 — — 生产过程中的几个美分的价格差异加起来数以百万计的特定种类的设备正在进行时。 如果该设备不能处理所需的安全协议,问题是多大的损害,可能引起不具有该协议以及如何关闭该设备缺乏所需的功能和基础设施的需求之间的差距。

什么应该清楚的是任何流不是加密和签名的数据是容易被窃听和操纵。 当设备报告只传感器数据时,这是值得考虑的网络路径上的男子在中间操纵是任何人都可以想象有价值还是可以分析检测到。 结果有时可能发送中清除的数据确定。 指挥和控制路径是另一回事 ; 作为行为的一种设备可以通过网络远程控制,我不能认为一宗案件,我不想有保护至少具有完整签名的通信路径。 私隐的指挥和控制的值取决于使用案例。 如果有针对操纵的地方的缓解,设定恒温目标温度似乎并不是值得花加密功夫。

配上这篇文章的示例代码包括到云计算的通信流的从设备的两个变种。 第一次是多简化 Windows Azure 服务总线 API,要求 HTTPS 和不会获取一个访问控制令牌和直接交谈 Windows Azure 服务总线定期握手。

第二个路径使用相同的一般形状的 Windows Azure 服务总线 HTTP 协议来发送和接收邮件,但它通过使用密钥的消息,它包含创建 HMACSHA256 签名。 这不会偷听,从保护邮件,但它不会从操纵保护邮件,并允许检测重播攻击,包括一个唯一的邮件 id 时。 答复将使用相同的密钥签名。 因为服务总线还暂时不支持这种身份验证模式 — — 尽管这篇文章是一个很好的指标,微软正在积极思考这个问题 — — 则路径会使用自定义网关服务与资源调配服务一起主持。 自定义的网关检查带签名和 Windows Azure 服务总线上的剩余消息传递。 使用自定义的网关协议翻译也通常是合适的模型如果您需要的云计算系统发言的无数的专有设备协议之一。 但需要牢记的自定义网关方法的一件事是它需要扩展到同时发送的邮件,以便使网关层非常薄和无国籍人士是个好主意的设备的数量。

要么密密麻麻 HTTP 或 HTTPS Uri 的资源调配服务最终使得两个路径之间的区别。 在设备代码中,由 TokenProvider 处理差异。 在 HTTPS 案例中,设备谈到 Windows Azure 服务总线,直,而在 HTTP 案例中,资源调配服务会谈到自定义的网关。 这里的假设是 HTTP 情况,设备获取 preprovisioned 不暴露在未受保护的互联网通信路径上的机密密钥的情况下。 换言之,资源调配服务运行在工厂里,不是在 Windows Azure。

设备的代码具有 Windows Azure 服务总线的两个交互:将事件发送和接收的命令。 我要寄出事件每分钟一次后取得了新的温度读数,和我还将使用这种机会抓取任何挂起的命令,并执行它们。 要做我将会修订中所示的 TemperatureHumidityMeasurementComplete 方法图 2,并添加到 SendEvent 和 ProcessCommands 的调用来处理每分钟一次,如中所示 图 6

图 6 将事件发送和接收命令

void TemperatureHumidityMeasurementComplete(TemperatureHumidity sender,
  double temperature, double relativeHumidity)
{
  [...] (see Figure 2)
  if (settings.NetworkProvisioningCompleted &&
    DateTime.UtcNow - settings.LastServerUpdate >
      TimeSpan.FromTicks(TimeSpan.TicksPerMinute))
  {
    settings.LastServerUpdate = DateTime.UtcNow;
    SendEvent(this.lastTemperatureReading, this.lastHumidityReading);
    ProcessCommands();
  }
}
void SendEvent(double d, double lastHumidityReading1)
{
  try
  {
    messagingClient.Send(new SimpleMessage()
      {
        Properties = {
          {"Temperature",d},
          {"Humidity", lastHumidityReading1},
          {"DeviceId", settings.DeviceAccount}
        }
      });
  }
  catch (Exception e)
  {
    Debug.Print(ethernet.ToString());
  }
}
void ProcessCommands()
{
  SimpleMessage cmd = null;
  try
  {
    do
    {
      cmd = messagingClient.Receive(TimeSpan.Zero, ReceiveMode.ReceiveAndDelete);
      if (cmd != null && cmd.Properties.Contains("Command"))
      {
        var commandType = (string)cmd.Properties["Command"];
        switch (commandType)
        {
          case "SetTemperature":
            if (cmd.Properties.Contains("Parameter"))
            {
              this.settings.TargetTemperature =
                double.Parse((string)cmd.Properties["Parameter"]);
              this.RedrawDisplay();
              this.temperatureHumidity.RequestMeasurement();
              StoreSettings(this.settings);
            }
            break;
        }
      }
    }
    while (cmd != null);
  }
  catch (Exception e)
  {
    Debug.Print(e.ToString());
  }
}

SendEvent 方法使用邮件客户端,获取初始化后可用的网络连接。 邮件客户端是一个小版本的 Windows Azure 服务总线 API 能够发送和接收邮件并从服务总线队列、 主题和订阅。 方法使用同一客户端读取的 ProcessCommands 命令从该设备的订阅并处理它们。 现在,该设备只了解 SetTemperature 命令同一个参数,指示绝对温度,将设置为一个数值 (在摄氏温度,顺便说一下)。 介意 ProcessCommands 指定 TimeSpan.Zero 超时属性从 Windows Azure 服务总线订阅中,接收消息,表明它不愿意等待消息到达。 我想要抓住一条消息,只是如果有一个可用和立即回。 这减少了自定义的网关的通信 (应使用 HTTP 和有一个地方),并且不需要我继续接收循环设备上打开。 这种权衡是滞后时间。 在最坏的情况下,命令有一分钟的延迟。 如果这是一个问题,您可以使用的较长超时,导致长时间轮询 (该图书馆支持),并命令延迟到几个毫秒,压低。

匹配的服务器端,用于接收事件,并将命令发送到已注册的所有设备,通过将消息放到该主题,只需遵循 Windows Azure 服务总线 API 的常规规则,然后是你可以下载,示例代码的一部分,所以我会省略该代码在这里。

接近尾声了

这一系列事情互联网的目标是提供一些洞察种技术我们这里在微软启用连接设备样机和发展工作。 我们还想要显示 Windows Azure 服务总线和分析技术 (StreamInsight 可以如云技术如何帮助您管理数据流量,从和连接的设备 ; 如何创建大型云架构处理大的很多设备 ; 和如何从他们聚合和摘要信息。

我谨此陈的路上,建立一种嵌入式的设备,您可以放在任何家庭网络和远程控制从任何其他网络,这是很酷,如果你问我。

我相信我们在这次旅行非常早期阶段。 在谈到 Microsoft 客户从各地,我见过,一个大浪的连接和定制设备是的路上,这是一个很大机会.net 开发人员和创新公司寻求建立云服务来连接到这些设备,并将它们与其他连接的资产组合以创造性的方式。 让我们看看你能做什么。

Clemens Vasters 是在 Windows Azure 服务总线团队的主要技术负责人。Vasters 已从最早的孵化阶段和 Windows Azure 服务总线,其中包括推式通知和高规模信号为 Web 和设备上的技术特点路线图工程团队。他也是一个经常会议议长和体系结构课件作者。跟随他在 Twitter 上 twitter.com/clemensv

由于下面的技术专家,检讨这篇文章:Elio Damaggio, Todd Holmquist-Sutherland, Abhishek Lal, Zach Libby, Colin Miller and Lorenzo Tessiore