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

Azure IoT Hub 测试器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (14投票s)

2017 年 3 月 31 日

CPOL

33分钟阅读

viewsIcon

74446

downloadIcon

3696

设计和实现小型工具,用于使用虚拟 MQTT 设备探索 Azure IoT Hub

目录

特点

  • 用于通过 MQTT 直连协议与 Azure IoT Hub 接口的虚拟 MQTT 设备
  • 发送模拟遥测数据(定期或手动)
  • 每个 MQTT 设备都托管在自己的 AppDomain 中
  • 通过 REST 端点探索 Azure IoT Hub
  • MQTT v3.1.1 协议,端口 8883
  • 根据 MSDN 文档直接使用 MQTT 协议
  • 无设备 SDK
  • 使用 M2Mqtt - .Net 的 MQTT 客户端库
  • 多租户使用(打开多个具有多个命名空间和多个虚拟设备的实例)
  • REST API 请求(模板)

版本 1.1 中的新功能

  • 在模拟设备中添加“模块”节点
  • 设备流模拟(仍处于预览阶段)
  • 使用设备连接字符串连接到 Azure IoT Hub
  • 连接到 **Azure IoT Central** 的设备模拟
  • 连接到 Azure IoT Central 的 **即插即用** 设备模拟(仍处于预览阶段)
  • JSON 格式文本的样式高亮

引言

总的来说,物联网 (IoT) 是一个快速增长的互联设备网络,用于收集和交换数据。Microsoft Azure 平台内置了从 端到端 基础设施,可以非常快速地将您的设备接入数据流管道,在实时过程中分析、管理和可视化您的设备。所有设备都通过一个称为“Azure IoT Hub”的中心连接到 Azure IoT 基础设施,如下图所示

设备、应用程序等使用特定的协议(MQTT、AMQP、HTTPs)发送遥测数据,并在需要时从云后端进程接收非遥测数据。数据收集和交换服务以安全可靠的方式实时提供。

Azure IoT Hub 代表了设备连接的入口组件,所有进出数据都通过此云服务,该服务具有多个端点,分为两类:面向设备的端点和面向服务的端点。有关此层次结构的更多详细信息,请在此处 查找

了解和理解 IoT Hub 端点的范式、它们的协议、数据交换、行为、功能等非常重要。

例如,以下屏幕截图显示了智能手机(Device1)和 Raspberry Pi3(Device2)这两个设备的连接。Device1Device2 希望通过 Azure IoT Hub 进行通信

可以看到,每个设备(设备)都需要向/从 Azure IoT Hub 发送和接收数据,在这种情况下,需要使用 Azure Function 来处理某种桥接。基本上,在此过程中至少涉及 Azure IoT Hub 的六个端点。最好有一个可用于故障排除、模拟和测试的工具。

从面向服务的端点来看,Microsoft SDK 包含一个非常有用的微型工具,称为 Device Explorer Twin。此工具可以模拟消费和调用 Azure IoT Hub 后面的数据,例如,捕获遥测数据(D2C 消息),向设备发送非遥测数据(C2D 消息),调用直接设备方法等。

这很棒。那么 Azure IoT Hub 的另一边,即面向设备的这一边呢?嗯,这可以通过本文包含的 Azure IoT Hub Tester 来实现。Azure IoT Hub Tester 可以用于“模型优先”方法,即设备尚不存在,可以为其创建虚拟设备以进行端到端处理。

以下屏幕截图显示了 Azure IoT Hub Tester 的位置,它有助于模拟我们的示例中的 Device1Device2

最近,Microsoft 在 Azure IoT Hub 中引入了名为 Device Twin 的新功能。从面向设备的观点来看,新功能目前仅受支持 MQTT 协议的设备支持,因此 Azure IoT Hub Tester 内置了用于 MQTT 协议的虚拟设备。

请注意,Azure IoT Hub 不是一个通用的 MQTT 代理用于发布/订阅通信,而是用于 Azure 物联网的 MQTT 代理的子集。换句话说,Azure IoT Hub 已经预先构建了所有发布者和订阅者的主题,无法创建自己的主题等。有关 Azure IoT Hub 对 MQTT 的支持的更多详细信息,请在此处 查找

Azure IoT Hub Tester 的设计和构建是为了创建虚拟 MQTT 设备,这些设备托管在各自的应用程序域中,并准备好与 Azure IoT Hub 集成。此外,该测试工具还可以根据 MSDN 文档为设备和服务的(云)端点提供 REST API 调用。

正如我之前提到的,可以使用 Device Explorer Twin 来探索和模拟面向服务的端点。以下屏幕截图显示了使用 Device Explorer Twin 探索 Raspberry 生成的遥测数据的示例

因此,通过 Azure IoT Hub 进行测试、模拟、探索和管理各个服务端点的能力,将使我们能够更好地理解 Azure IoT 基础设施的核心组件。下图显示了这些“神奇的”微型工具。

Device Explorer Twin

Azure IoT Hub 测试器

要探索 Microsoft Azure IoT Hub 的功能,我们至少需要一个免费的 Azure 帐户,并创建一个免费的 Azure IoT Hub。一旦我们拥有了它,就可以像下图所示那样将我们的测试工具连接到 Azure IoT Hub

这太棒了。上述场景将允许我们创建虚拟设备,向 Azure IoT Hub 发送遥测和/或非遥测数据,查看哪些数据被摄取到默认事件中心,探索设备孪生属性,调用设备直接方法等,当然,服务方面任何更改都会传播到虚拟设备,例如在特定主题上接收消息。此外,根据需要,虚拟设备可以定期发送遥测样本,包含随机值和/或显式值,包括实时值。

以下屏幕截图显示了更多连接到 Azure IoT Hub 组合(如 IoT Hub 和 Stream Analytics)的测试工具

可以看到,在 IoT 管道中使用测试工具,我们可以模拟和探索在集成组件之间流动的遥测和非遥测数据。上面的示例显示了流作业到 Azure Service Bus 和 Azure Storage 的输出。两者都可以通过测试工具进行探索。本文稍后将提供更多 Azure IoT Hub Tester 的示例。

好的,让我们来描述它的概念和设计。我假设您对 Azure IoT 基础设施有所了解。

概念与设计

Azure IoT Hub Tester 的概念是基于创建一个虚拟 MQTT 设备,该设备使用此处 描述 的 MQTT 协议集成到 Azure IoT Hub。每个虚拟设备都托管在自己的应用程序域中,并通过 WCF 管道与 Windows Form 所在的默认域进行内部通信。基于在 Azure IoT Hub 中注册的设备,测试工具将(在第一步)创建其私有应用程序域,其中可以托管 MQTT Client(代理)。一旦成功创建了设备客户端,它就可以连接到 Azure IoT Hub 并订阅所有特定主题,如孪生、直接方法、C2D 消息等。

下图简要说明了面向设备和面向服务的两边处理的端点类型

基本上,设备消息分为两类。第一类用于发送 D2C 消息、接收 C2D 消息和文件上传。第二类是称为 Device Twin 的新功能组。请注意,目前新功能仅受 MQTT 协议支持。测试工具可以处理多个租户,每个租户都有自己的独立应用程序域中托管的多个设备,因此如果虚拟设备因任何原因“死亡”或断开连接,都不会对其他设备产生任何影响。

Azure IoT Hub Tester 的概念和设计与我在 文章 中详细描述的 Azure Service Bus Tester 类似。

此测试工具创建的每个虚拟设备将在“设备”根节点下拥有自己的节点。设备节点(代表虚拟 MQTT 设备)能够手动或每 3 秒定期发送遥测数据。消息负载必须是 JSON 格式的文本,包含显式值和/或使用别名值在发布过程中进行随机替换。设备节点的名称是 deviceId。该设备有 3 个子节点,每个节点代表一个特定主题的虚拟设备订阅者,如 twinmethods 和 C2D messages。例如,如果服务(云)更改了设备孪生的期望属性,那么它的虚拟设备将在孪生节点中收到通知消息。

下图显示了一个连接到 Azure IoT Hub(ba2017-iot)的 3 个设备的示例

如上图所示,Azure IoT Hub 命名空间 (ba2017-iot) 有一个 REST-API 节点。该节点用于 REST 调用。它根据 MSDN 文档 中描述的 REST API 分为更多类别。REST-API 节点是从与特定 Azure IoT Hub 命名空间相关的模板文件创建的。

好的,该展示一下了。让我们描述一下这个微型工具能为您做什么。我假设您对 Azure IoT Hub 有一些了解。

用法

在开始使用 Azure IoT Hub Tester 之前,我假设您已经阅读了 Azure IoT Hub 开发人员指南 文章,并且您拥有 Azure IoT Hub 帐户。请注意,该工具仅适用于 Azure IoT Hub,因此请准备好 Azure IoT Hub 的连接字符串。

连接到 Azure IoT Hub

右键单击根节点(Azure IoT Hubs)并选择“连接”菜单,如下图所示

单击“连接”将显示“NamespaceDialog”,我们在其中必须输入两列,即“Namespace”名称代表 Azure IoT Hub 及其“Connection String”,请参见下图

该测试工具可以连接到多个 Azure IoT Hub,只需填充上述“NamespaceDialog”即可。请注意,这些网格值会被持久化,因此下次我们需要连接到特定的 Azure IoT Hub 时,只需选择一个“grid row”即可。

按下 **OK** 按钮,测试工具将查找您命名空间的模板文件(例如:ba2017-iot.json),并将为该 Azure IoT Hub 命名空间创建一个树节点。下一步是展示这个示例。

注意

为了测试 Azure IoT Hub Tester,我创建了一个 Azure IoT Hub 帐户。此帐户有效期至 **2017 年 4 月 28 日**。

Namespace: ba2017-iotdemo
ConnectionString: HostName=ba2017-iotdemo.azure-devices.net;
SharedAccessKeyName=iothubowner;SharedAccessKey=uQsW/ekXOzeMrqvq9dEqLsR4Uf1zxZSjSTjGGzwmWEU=

请在 Azure IoT Hub 中注册您的设备,并将其用于测试。请注意,MQTT 协议不允许同一 deviceId 进行多次连接。

连接设备

在此步骤中,我将演示如何在测试工具应用程序域中创建虚拟 MQTT 设备。

右键单击“**设备**”节点。在右侧面板中,您应该看到所有已注册的设备。如果此网格为空,则表示您在选定的 Azure IoT Hub 中没有注册任何设备,因此请使用 Device Explorer Twin 或 Azure 门户注册一个。

如上图所示,我的 ba2017-iot 中心包含 3 个设备,如 DeviceB11myDevicemyFirstDevice。请注意,每次选择此“设备”节点(或单击“**刷新**”)时,测试工具都会连接到 Azure IoT Hub 以获取所有已注册的设备。

让我们选择第一个设备(如上图所示)。右键单击“设备”节点并选择“连接”操作。在此操作背后,测试工具需要完成所有魔术工作,例如创建托管在自己应用程序域中的已连接虚拟 MQTT 设备,以及 marshaling 接口并与 UI 界面表示所在的默认域进行通信,请参见下图

现在,测试工具拥有一个虚拟 MQTT 设备(deviceId = DeviceB11),已连接到 Azure IoT Hub(ba2017-iot),并已准备好用于 Azure IoT Hub 内置的所有面向设备的特性。

还有一点,测试工具能够使用 REST API 注册设备。下图展示了此调用的示例

好的,这很棒。让我们来探索这个虚拟 MQTT 设备,例如 Device Twin、Direct Method、D2C 和 C2D 消息。对于这类用例,我们需要打开 Device Explorer Twin 工具来访问面向服务的端点。Device Explorer Twin 工具将代表 Azure IoT Hub 后端服务。我假设您对此 Microsoft 工具已有经验。

Device Twin

Device Twin 可以通过服务填充其期望属性,以及设备填充其报告属性这两种方式来演示。

以下屏幕截图显示了一个示例,其中 myDevice 的期望属性已由 Device Explorer Twin 工具填充

如上图所示,名为 abc 的期望属性已用值 123456 填充。单击“**发送**”按钮后,我们可以在 Azure IoT Hub 测试工具中看到其虚拟设备(在本例中为 myDevice)在孪生节点中收到了消息。这是一个通知消息,表示设备孪生的期望属性已更改

这太棒了!设备已收到通知,因此设备有责任基于设备孪生版本等的评估来更新其报告的属性。

因此,键入发布者的负载,例如,如下图所示

单击“**发布**”按钮将此消息发送到 Azure IoT Hub。

为了验证此步骤,我们可以单击 Device Explorer Twin 工具上的“**刷新**”按钮

您可以创建更多属性,填充或删除它们等,以查看 Azure IoT Hub 如何处理期望属性和报告属性之间的同步。

在查看所有 Device Twin 属性的情况下,右键单击“**Twin 节点**”并选择“**Get**”。Azure IoT Hub 将在主题上向您发送一条消息。

Direct Method

Direct Method 允许通过面向服务的端点以同步方式调用设备方法。它类似于 RPC 调用。下图显示了 Device Explorer Twin 工具如何调用 myDevice 上的方法

单击“**Call Method**”按钮,Azure IoT Hub 将向 myDevice 发送消息。

以下屏幕截图显示了该消息在方法节点中

现在,虚拟 MQTT 设备(myDevice)有大约 60 秒的时间将响应发送回 Azure IoT Hub,否则调用将因调用方出现错误消息而终止。

因此,键入一些负载,例如如下图所示,然后单击“**Publish**”按钮

之后,您可以在 **Device Explorer Twin** 工具中看到响应,包括返回状态码

发送 D2C 消息

这是一个演示发送称为设备到云 (D2C) 消息的遥测事件的测试。我们的虚拟设备(myDevice)将向 Azure IoT Hub 发布事件,在面向服务的方面,我们将有一个 Device Explorer Twin 工具来查看这些消息。测试在 myDevice 节点上开始,如上图所示

可以看到,测试工具在发布者负载中提供了一些示例。我已突出显示了内置值,这些值可以在运行时发布期间用随机值替换($DateTime.UtcNow 除外)。此测试工具功能允许为每次单击“**Publish**”按钮生成随机遥测事件。

使用 **Device Explorer Twin**,我们可以看到 D2C 消息,如下图所示

这太棒了!只发送了一个遥测事件。要定期发送更多虚拟设备的遥测样本,我们需要将此 JSON 对象作为数组的一个项,请参见下图中的突出显示的括号。请注意,测试工具将以 3 秒的周期发送最多 100 个随机样本。

要停止此“自动采样”过程,只需单击下图所示的“**Stop Sampling**”菜单

注意 1:如您所见,上面的上下文菜单有一个“**Load Samples**”项。使用此功能可从文件系统加载样本。文件内容必须是遥测样本的 JSON 数组。

注意 2:测试工具允许从多个虚拟设备同时采样。

注意 3:您可以通过在主题的查询字符串中添加属性包(名称/值对)来发送非遥测事件(消息)到 Azure IoT Hub 路由,例如

devices/Device2/messages/events/location=BA&$.to=Device1

接收 C2D 消息

这是由 Device Explorer Twin 工具生成的 Cloud-to-Device (C2D) 消息的演示,由 Azure IoT Hub Tester 托管的虚拟 MQTT 设备接收。

让我们输入一些文本,例如 Hello Device,如下图所示

单击“**Send**”按钮,C2D 消息将被发送到 Azure IoT Hub 并存储在面向设备的队列中。

虚拟设备已订阅该主题,因此它将拉取并 ACK(这是 MQTT 设备)收到的消息。此 ACK 反馈将发送到 Device Explorer Twin 监视器(参见上图)。

回到虚拟设备。在我们的示例中,设备 myDevicemessages 节点中收到了此 C2D 消息,请参见下图

可以看到,上述 C2D 订阅者负载显示了我们的文本消息,例如 Hello Device

以上是关于虚拟 MQTT 设备的内容,接下来我将描述 Azure IoT Hub Tester 的附加功能,例如 REST API 请求。

REST-API

REST-API 节点允许发送 http(s) 请求到 url 地址。请求头可以简单地作为名称/值对插入,用冒号分隔。例如,content-type:application/json。每行代表一个头。

请求中有一个特殊的头,称为 Authorization。如果此头不存在,运行时客户端代理将基于与 Azure IoT Hub 的连接生成一个。

使用此 REST-API 节点非常直接,类似于其他 REST 工具,例如设置 URL、头、方法,然后单击“**Send**”按钮。请求将返回响应状态和负载。

正如我前面提到的,Azure IoT Hub 有许多端点,其中许多也可以通过 REST API 进行处理,因此 Azure IoT Hub Tester 允许以最少的设置提供这些 REST API 调用。

根据 MSDN 文档 IoT Hub REST,每个请求都由一个简单的 JSON 对象描述,该对象将用于在测试工具中创建其树节点。每个这样的请求定义代表 JSON 数组中的一个项。请注意,存储数组的文件名必须是 Azure IoT Hub 命名空间的名字,例如 ba2017-iot,并且带有 json 扩展名,否则测试工具将找不到它。

此请求模板的结构显示在以下代码片段中

[
  {
    "category": "DeviceApi",
    "name": "QueryDevices",
    "method": "POST",
    "url": "/devices/query?api-version=2016-11-14",
    "headers": "content-type:application/json | x-ms-max-item-count:100",
    "payload": {
      "query": "SELECT * FROM devices WHERE deviceId='myFirstDevice' "
    },
    "description": null
  },
  { 
    ...
  }
]

如上代码所示,头部分隔符是管道字符(|),URL 地址只能包含路径和查询,协议和域将在运行时由测试工具添加。如果 URL 地址包含完整地址(协议、域等),测试工具将无修改地接受它。

文章下载中包含 REST-API 模板的示例,名称为 ba2017-iot.json,请根据您的命名空间名称重命名它。

好的,让我们继续描述我们的工具。一旦我们连接到 Azure IoT Hub,测试工具将创建一个树节点,其中包含两个子实体:REST-APIDevices。如果测试工具在二进制文件夹中找到模板文件(在本例中为 ba2017-iot.json),它将处理数组中的每个模板项并创建代表它的树节点。

以下屏幕截图显示了 DeviceApi 类别中 QueryDevices 请求模板的示例

可以看到,上面的 Rest Client 面板中的 POST 请求已准备好发送。根据需要,可以修改查询的请求负载。

单击“**Send**”按钮,以下屏幕截图将显示其响应

让我们探索另一个请求,例如,请求调用设备上的直接方法。

TwinApi 类别中选择 SendDirectMethod,并假设您已经创建了一些虚拟设备(在我的示例中,是 myFirstDevice)。

选择 SendDirectMethod 节点后,请求将显示模板值。在 url 文本框中,如果需要,请将 myFirstDevice 替换为您的 deviceId,否则请求将以错误终止。

将上述请求发送到 Azure IoT Hub 后,虚拟设备将收到一个方法请求消息,并在其响应发布后,以下响应示例将显示在调用方的响应面板中

这太棒了。上面的示例表明,Azure IoT Hub 工具可以在两个方面使用:面向设备(MQTT)方面和面向服务(REST)方面。

现在,让我们演示一些我们无法在虚拟 MQTT 设备中做到的事情,那里没有相应的主题,但我们可以通过 REST API 调用来处理。

设备文件上传

此演示需要配置您的 blob 存储以实现文件上传功能。从设备的角度来看,文件上传分为三个步骤,并通过 Http 协议处理。

步骤 1

设备将发送请求以获取用于创建完整 URL 地址(包括 SAS 令牌)的引用,用于 blob 上传调用。以下屏幕截图显示了此示例

单击“**Send**”按钮后,Azure IoT Hub 将发送响应

可以看到,在上述响应负载中,有关于生成 blob 上传 URL 地址的信息,还有一个非常重要的属性,即 correlationId。此值代表在 Azure IoT Hub 中创建的状态机 ID。请注意,文件上传请求的最大数量为 10,并且如果未发送完成(步骤 3),则每个状态机都有一个 TTL 时间。TTL 时间的范围是 1-48 小时。

以下屏幕截图显示了测试工具日志面板,其中将显示 correlationId 和为 blob 上传生成的 URL 地址

第二步

此步骤用于 blob 上传。选择下一个节点,如 UploadBlobFile

设备有足够的时间将 blob 文件上传到 Azure IoT Hub 引用的存储容器。以下屏幕截图显示了此请求调用。请注意,必须添加特定的 blob 类型标头,并且 URL 地址可以复制/粘贴到 url 文本框中。

当请求完成时,状态“**Created**”将显示其成功过程。

步骤 3

这是文件上传状态机中最后也是非常重要的一步。此步骤表示上传过程已完成。请注意,如果缺少此步骤,设备将不得不从步骤 1 等重新开始文件上传过程。

因此,选择下一个节点,如 NotifyUploadBlobFile,并将 correlationId 复制/粘贴到请求负载中,如下图所示

基于最后一个请求,Azure IoT Hub 可以(如果启用了此选项)生成一个使用 AMQP 协议的文件上传通知。

以上是关于设备文件上传的内容。

通过 REST 发送 C2D 消息

这是一个高级示例,用于展示当 Azure IoT Hub 端点需要 AMQP 协议时,如何轻松使用 Azure Function。基本上,在面向服务的方面,有几个端点需要处理 AMQP 协议,例如用于 Cloud-to-Device (C2D) 消息的端点、C2D 反馈通知和文件上传通知。

以下屏幕截图显示了此概念。我们可以创建一个 Azure Function(AFN),例如 HtppC2D,用于 http 触发器,添加 Azure IoT Hub SDK(Microsoft.Azure.Devices)的 Nuget 包,并将 http 请求转换为 AMQP 消息。

http 请求模板应如下所示

请求模板

 {
    "category": "Misc",
    "name": "HttpC2D",
    "method": "POST",
    "url": "https://xxxxxxxxxx.azurewebsites.net/api/HttpC2D?code=XXXXXXXXXXXXX&name=myDevice",
    "headers": "accept: application/json | content-type: application/json | 
                iothub-messageId:00000000000000000000000000000000 | 
                iothub-correlationId: 1234567890 | iothub-app-location: BA",
    "payload": {
      "name": "Hello Device",
      "ts": "$DateTime.UtcNow"
    },
    "description": null
  }

请注意,URL 查询字符串包含参数 name,例如 deviceId,请求将被转发到此处。

以下代码片段显示了一个 HttpC2D 函数实现的示例。请注意,有一个应用程序设置用于配置您的 Azure IoT Hub 的字符串。

run.csx

using System;
using System.Text;
using System.Net;
using System.Runtime.Serialization;
using System.Net.Http.Headers;
using Microsoft.Azure.Devices;
using System.Configuration;

public static async Task<httpresponsemessage> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info("C# HTTP trigger function processed a request.");

    // parse query parameter
    string name = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
        .Value;

    if (string.IsNullOrEmpty(name))
        return req.CreateResponse(HttpStatusCode.BadRequest, "Missing name of the device");

    // Get request body
    var payload = await req.Content.ReadAsByteArrayAsync();

    // copy payload
    Message c2dmsg = new Message(payload);

    // copy properties
    c2dmsg.Ack = DeliveryAcknowledgement.Full;

    // Copy all Http Headers
    foreach (KeyValuePair<string, ienumerable="">> header in req.Headers)
    {
        log.Info($"H: {header.Key} = {header.Value.First()}");

        if (header.Key.StartsWith("iothub-app-"))
            c2dmsg.Properties.Add(header.Key.Substring(11), header.Value.First());
        else if (header.Key.StartsWith("iothub-correlationid"))
            c2dmsg.CorrelationId = header.Value.First();
        else if (header.Key.StartsWith("iothub-messageid"))
            c2dmsg.MessageId = header.Value.First();
        else if (header.Key.StartsWith("iothub-userid"))
            c2dmsg.UserId = header.Value.First();
        else if (header.Key.StartsWith("iothub-to"))
            c2dmsg.To = header.Value.First();
    }

    // for test purposes
    c2dmsg.Properties.Add("afn-timestamp", DateTime.UtcNow.ToString("o"));

    // create proxy
    string connectionString = ConfigurationManager.AppSettings["myIoTHub2"];
    var client = ServiceClient.CreateFromConnectionString(connectionString);


    // send AMQP message
    await client.SendAsync(name, c2dmsg);

    return req.CreateResponse(HttpStatusCode.NoContent);
}

function.json

{
  "bindings": [
    {
      "authLevel": "function",
      "name": "req",
      "type": "httpTrigger",
      "direction": "in",
      "methods": [
        "post"
      ]
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    }
  ],
  "disabled": false
}

project.json

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "Microsoft.Azure.Devices": "1.2.3"
      }
    }
   }
}

一旦我们部署了 HttpC2D 函数和(不要忘记使用您的函数 URL 地址和您的 deviceId)文件中的 HttpC2D 请求模板,我们就可以像下图所示那样调用它

下图显示了虚拟设备(Device1)收到的 C2D 消息

在上述高级示例中,我们演示了如何通过使用预定义的 Http 模板来轻松扩展此工具。我们可以使用相同的功能来为 C2D 反馈通知等创建 AFN

实现

首先,以下是先决条件:

  • Visual Studio 2017

  • M2Mqtt - .NET 版 MQTT 客户端库 4.3.0

  • Microsoft Azure IoT Hub 帐户

  • 互联网连接

  • 下载本文的程序包(源代码和/或可执行文件)(可选择创建程序集)

Azure IoT Hub Tester 的实现概念和设计与我的 Azure Service Bus Tester 文章类似。它基于一个模型,其中每个设备都托管在自己的应用程序域中,以实现彼此之间的完全隔离。这种分布式模型要求在托管 Windows Form 的默认域和设备域之间进行跨域通信。每个设备都由远程对象 MqttClientActivator 表示,其实例被 marshaling 到特定的应用程序域。 '

下图显示了 MqttClientActivator

我将描述该类实现的一些方法。

以下代码片段显示了创建 MqttClientActivator 的方法

public static MqttClientActivator Create(AppDomain appDomain, ConfigData config)
{           
  string _assemblyName = Assembly.GetAssembly(typeof(MqttClientActivator)).FullName;
  MqttClientActivator activator = appDomain.CreateInstanceAndUnwrap
      (_assemblyName, typeof(MqttClientActivator).ToString()) as MqttClientActivator;
  activator.SetClient(config);
  return activator;
}

可以看到,这是一个直接的编码,用于 marshaling 特定域中的对象。一旦我们拥有了 activator 的实例,我们就可以根据配置需求对其进行设置。

这显示在下面的 private 方法中

private void SetClient(ConfigData config)
{
  try
  {
    if (_client == null)
    {
      _client = new MqttClient(config.BrokerAddress.Trim(), 
                config.BrokerPort, true, null, null, MqttSslProtocols.TLSv1_2);
      _client.ProtocolVersion = MqttProtocolVersion.Version_3_1_1;

      // event when connection has been dropped
      _client.ConnectionClosed += Client_ConnectionClosed;

      // handler for received messages on the subscribed topics
      _client.MqttMsgPublishReceived += client_MqttMsgPublishReceived;

      // handler for publisher
      _client.MqttMsgPublished += Client_MqttMsgPublished;

      // handler for subscriber 
      _client.MqttMsgSubscribed += Client_MqttMsgSubscribed;

      // handler for unsubscriber
      _client.MqttMsgUnsubscribed += client_MqttMsgUnsubscribed;

      _name = config.BrokerAddress + "/" + config.Name;

      this._configData = config;
      this.AddToStorage(this);

      LogMessage($"[{this.Name}] Client has been created in the appDomain 
                {AppDomain.CurrentDomain.FriendlyName}");
    }
    else
    {
      throw new InvalidOperationException("The MqttClient has been already setup");
    }
  }
  finally
  {
    CallContext.FreeNamedDataSlot("_config");
  }
}

可以看到上面的实现,activator 是 MqttClient 对象的一个包装器,允许我们与其他应用程序域进行通信。一旦我们设置了 MqttClient 代理的所有回调,我们就可以将其引用存储到进程数据槽中。

以下代码片段显示了一个 public 方法的实现,用于将设备 Connect 到 Azure Iot Hub。请注意,此方法可以由任何 appdomain 调用

public void Connect(string password = null)
{
  if (_client != null)
  {
    try
    {
      if (_client.IsConnected == false)
      {
        // update a SAS for reconnection
        if (string.IsNullOrEmpty(password) == false)
          this._configData.Password = password;

        byte connCode = _client.Connect(this._configData.Name, this._configData.Username, 
                        this._configData.Password, false, MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE,
                        false, "$iothub/twin/GET/?$rid=911", "Disconnected", false, 60);

        if(connCode != 0)
          throw new Exception($"Connect failed, code = {connCode}");

        _topics = new string[] { "$iothub/methods/POST/#", 
                  $"devices/{_configData.Name}/messages/devicebound/#", 
                  "$iothub/twin/PATCH/properties/desired/#", "$iothub/twin/res/#" };

        ushort subCode = _client.Subscribe(_topics, new byte[] 
                         { MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE, 
                         MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE, 
                         MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE, 
                         MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE });

        LogMessage($"[{this.Name}] Connected", "HighlightInfo");
      }
    }
    catch (Exception ex)
    {
      RemoveFromStorage(this);
      LogMessage($"[{this.Name}] Connecting device failed: {ex.Message}", "Error");
      throw ex;
    }
  }
}

可以看到,上面的实现遵循了 Azure IoT Hub 文档要求的所有连接和订阅者主题。因此,一旦设备(代理)完成了上述方法,消息就可以由以下回调接收和处理

    private void Client_ConnectionClosed(object sender, EventArgs e)
    {
      LogMessage($"[{this.Name}] Connection closed", "Warning");
      var payload = new MqttMsgEventArgs("$iothub/clientproxy/", 
                    Encoding.UTF8.GetBytes("Disconnected"), false, 0, false);
      this.ForwardMessageAsync(payload).Wait();
    }

    void client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
    {
      LogMessage($"[{this.Name}] Subscriber received at {e.Topic}", "HighlightInfo");
      this.ForwardMessageAsync(new MqttMsgEventArgs(e)).Wait();
    }

    private void Client_MqttMsgPublished(object sender, MqttMsgPublishedEventArgs e)
    {
      LogMessage($"[{this.Name}] Response from publish {e.MessageId.ToString()}");
    }

    private void Client_MqttMsgSubscribed(object sender, MqttMsgSubscribedEventArgs e)
    {
      LogMessage($"[{this.Name}] Response from subscribe {e.MessageId.ToString()}");
    }
    private void client_MqttMsgUnsubscribed(object sender, MqttMsgUnsubscribedEventArgs e)
    {
      LogMessage($"[{this.Name}] Response from unsibscribe {e.MessageId.ToString()}");
    }   
}

可以看到,上面的实现使用 private 方法 ForwardMessageAsync 将消息转发到默认(UI)域,以便在 treeview 模型中显示。通信通道使用 WCF 模型,通过命名管道。

下一个代码片段显示了一个 public 远程方法,用于在主题上发布消息

public ushort Publish(string topic, string payload, byte qos, bool retain)
{
    if (_client.IsConnected)
        return _client.Publish(topic, Encoding.UTF8.GetBytes(payload), qos, retain);
    else
        return 0;
}

可以看到,上面的实现实际上是 M2Mqtt 库中原始方法的 marshaling 包装器。

因此,远程对象 MqttClientActivator 实现了所有 public 方法,这些方法对于跨应用程序域边界与 MqttClient 代理进行通信是必需的。下面的示例展示了从默认(UI)域调用它的便捷性,例如,当我们单击发布消息时

MqttClientActivator client = HostServices.Current.GetClient(name);
var code = client.Publish(e.Topic, jsontext, e.QoS, e.Retain);

第一步是根据名称获取 MqttClientActivator 的引用。一旦我们有了它,我们就可以调用它的任何 public 远程方法,例如 Publish 方法。

以上是关于实现的内容。

结论

本文为您提供了一个用于 Azure IoT Hub 的微型测试工具。它可以帮助您在评估和探索 Azure IoT Hub 端点、开发 MQTT 设备、对 IoT 数据进行故障排除或模拟设备 C2D 和/或 D2C 消息时提供帮助。希望您觉得它有用。

附录 A - 版本 1.1

Azure IoT Hub 中有许多新功能,因此这是该测试工具的第一个更新,旨在支持即将推出的即插即用 IoT 设备功能。

让我们分别描述 Azure IoT Hub 的每项新功能以及 Azure IoT Hub Tester 如何处理它们

A1. 设备模块

根据 Azure IoT Hub 的新功能,如模块和流,设备 treeNode 已得到扩展。以下屏幕截图显示了 device1 的示例

请注意,Modules 节点与设备节点的处理方式相同,因此通过选择此节点,我们可以在右侧看到该设备下所有已注册的模块。以下屏幕截图显示了 device1 中模块的示例。选择模块并双击行,模块将被添加到设备模拟中

A2. 设备流

目前(2019 年 10 月),设备流 功能仍处于公共预览阶段,因此一旦达到 GA,它可能会发生变化。设备流功能可以通过此测试工具进行模拟/测试。设备流的概念类似于直接方法,其中此同步通信用于设备和云后端应用程序之间的握手和切换到 Web 套接字通信,通过 Azure IoT Hub 协调器。

以下屏幕截图显示了设备流的 REST 客户端(调用方和使用者)

当设备接受后,此客户端查询将发送响应(如设备方法)以确认并切换到 Web 套接字接收器

为了测试目的,您可以通过 readwrite ServiceStream Buffer 向 readonly DeviceStream Buffer 发送一些内容。

请注意,与文件上传功能类似,设备不需要了解任何有关端点和授权令牌的详细信息。所有通信元数据对设备来说都是透明和动态的。它们在握手阶段从客户端调用方通过 Azure IoT Hub 获取。

为了对设备流有某些实现上的认识,以下代码片段显示了位于 Form1.cs 文件中的接收器部分 - 方法 DeviceStreamWorker

using (var wsClient = new ClientWebSocket())
{
    wsClient.Options.SetRequestHeader("Authorization", $"Bearer {nstate.Tag}");
    wsClient.ConnectAsync(new Uri(node.Text), ct.Token).Wait();
    receiveResult = wsClient.ReceiveAsync(new ArraySegment<byte>(buffer, 0, buffer.Length), 
                    ct.Token).Result;
    nstate.Payload = Encoding.UTF8.GetString(buffer, 0, receiveResult.Count);
    wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, String.Empty, 
                        ct.Token).ConfigureAwait(false);
}

请注意,Bearer 令牌和 Uri 地址是从设备订阅者在主题:$iothub/streams/POST/# 上收到的消息中获取的。

A3. 使用设备连接字符串进行设备连接

Azure IoT Hub Tester 允许设备基于共享访问策略连接到 Azure IoT Hub。一旦我们有了该策略,例如 iothubowner 策略,测试工具就可以从 Azure IoT Hub 的面向设备和服务的所有端点进行操作。

如果 Azure IoT Hub 的连接字符串不可用(但我们只有设备连接字符串),或者换句话说,我们只能访问 Azure IoT Hub 的面向设备的端点,那么此新的测试工具更新将为您提供帮助。

在这种情况下,只需在 Namespace 对话框网格中输入设备连接字符串,并键入该连接字符串中的 Azure IoT Hub 命名空间。

下图显示了此说明。如您所见,第一项是具有 iothubowner 策略的 Azure IoT Hub 的连接字符串。

请注意,当您在同一命名空间下的 NamespaceDialog 中添加更多设备时,所有这些设备都将显示在“设备”网格中,以便连接。

A4. 将虚拟设备连接到 Azure IoT Central

Azure IoT Central (Azure IoTC) 是一个构建在 Azure IoT Hub 之上的 SaaS 应用程序,用于简化 IoT 开发。Azure IoTC 抽象了其内部 Azure IoT Hub 的所有复杂性,从而允许 IoTC 完全控制内部 IoT Hub。Azure IoTC 为您提供了对其基础设施的特定访问权限。其中一种访问方式是通过元数据(scopeIddeviceIdprimary key)创建设备连接字符串,以便设备连接到 Azure IoTC。让我们看看如何将其用于我们的 Azure IoT Hub Tester 和虚拟 MQTT 设备。

我假设您对 Azure IoT Central 有一些知识和经验。我将使用我的 IoT Central 和两个设备,例如 device1device2

让我们连接 Azure IoT Hub Tester 模拟的 device1 到 Azure IoT Central。以下屏幕截图显示了我的 device1

要获取用于创建设备连接字符串的元数据,请单击 Connect。您应该会看到设备连接到 Azure IoT Central 的详细对话框,请参见我的对话框

我在上面的设备连接对话框中为我们的需求突出显示了以下属性:ScopeIDDeviceIDPrimaryKey

现在,回到我们的 Azure IoT Hub Tester 并打开 **NamespaceDialog** 输入设备连接字符串。

使用复制/粘贴将这些属性从 IoTC 复制到以下文本框,以查看它们如何显示

请注意,每个属性之间有一个“空格”分隔符。插入所有属性后,“**Get**”按钮将被启用,并等待按下以生成设备连接字符串。按下“**Get**”按钮后,设备连接将在黄色文本框中创建(或显示错误消息)。如果此连接字符串不在命名空间网格中,则“**Add**”按钮将被启用

按下“**Add**”按钮

可以看到,上述 Namespace 网格包含到 IoT Central 的设备连接字符串,因此当选择此行并单击“**OK**”按钮时,我们可以选择设备,就像从 Azure IoT Hub 中选择设备一样,请参见下图

这太棒了!现在我们可以模拟连接到 Azure IoT Central 的设备,探索设备孪生等。

正如我所提到的,IoTC 抽象了内部 IoT Hub、设备孪生等周围的所有实体,因此基于上图,其中孪生是从内部 IoT Hub 获取的,以下屏幕截图显示了期望属性如何映射到设备设置

可以看到,期望属性是一个对象

  "desired":{
    "num":{ "value":12 },
    "text":{ "value":"ABCD" },
    "mytoggle":{ "value":true }
    }

对于报告属性,它们是原始类型,请参见下文

"reported":{
    "battery":90,
    "fan":true,
    "echo":"ABCD1234",
    "echo2":"1111",
    "maxtemp":25
  }

我演示了 Azure IoT Hub 和 Azure IoT Central 抽象设备孪生的某些差异。请注意,响应和直接方法的行为存在很大差异。

A4.1 使用主(组)密钥为 Azure IoT Central 创建设备连接字符串

IoTC 提供了一个主(组)密钥,用于为 IoT Central 中注册的任何设备生成设备连接字符串。以下屏幕截图显示了如何获取它

上述主密钥可以插入到带有前缀字符“@”的文本框中。请参见下图。请注意,deviceId 是手动键入的

A5. 使用连接到 Azure IoT Central 的即插即用 IoT 设备

IoT 即插即用 IoT Plug and Play 功能仍处于公共预览阶段。其概念基于 Microsoft 即将发布的 数字孪生定义语言 (DTDL) 创建的 IoT Plug and Play 架构,其中核心架构描述了设备的功能。Azure IoT Central 为即插即用 IoT 设备开放了公共预览。让我们深入了解这个很棒的即将推出的功能。请注意,设备 CapabilityModel 架构是设备和 IoT 应用程序端的通用架构。

上面的屏幕截图显示了向模板集合添加新的 IoT PnP 设备。在我的例子中,选择了 AzureKit ESP32 设备。

基于此模板,我们可以创建一个新设备,请参见下面的对话框

在此步骤之后,此 IoTC 应用程序(如 iotc-preview)拥有一个已注册设备 azurekit-1。请注意,此应用程序有 7 天的免费试用期,最多支持 5 台设备。

从现在开始,我们可以看到 AzureKit 设备的 CapabilityModel 如何“插入”到 IoTC 应用程序中。以下屏幕截图显示了关于该设备的“ About”信息

可以看到,“**About**”属性是空的,这没关系,因为我们还没有使用它。

下一个页面“**Overview**”也面临同样的情况,数据缺失

下次单击“**Commands**”将显示设备上的所有调用方法及其输入。请注意,单击“**Run**”按钮后,响应将立即因设备未连接而结束,并且所有命令都处于同步调用模式。

要将 IoTC 设备 azurekit-1 添加到 Azure IoT Hub Tester,我们可以使用本文第 A4 段中描述的相同步骤。

以下屏幕截图显示了此条目到测试工具

之后,我们可以将设备 azurekit-1 连接到 IoT Central 应用程序。请注意,此设备仍然在我们的测试工具中,并且**不是** IoT 即插即用设备,它是一个常规设备。换句话说,没有 Capability Model 插入到此设备中。

好的,让我们将 Capability Model 添加到由我们的 Tester 连接的设备中。

首先,IoT Central 需要导出此 Capability Model,请参见下图

下图显示了 IoT Central 应用程序和我们的 Tester 之间的逻辑集成的概述,其中 Capability Model 代表了即插即用机制

在将加载的样本加载到 CapabilityModel 中之后,连接的设备将根据此架构重新配置。请参见下图

可以看到,Model 节点已添加到设备节点,Methods 节点显示了该设备可由模型架构处理的所有可能方法,当然,遥测样本已创建。请注意,遥测数据可以随机生成或显式插入。

IoT 即插即用设备的完整 CapabilityModel 可以在 Model 节点中进行探索(和编辑)。

可以看到,Model 节点有两个子节点。这些节点显示了报告的属性,因此通过复制/粘贴,我们可以填充设备孪生,请参见以下结果

请注意,设备孪生报告的属性不能通过一个简单的 null 设置来删除,就像我们可以为期望属性那样,测试工具能够为报告的集合中的每个属性设置 null

好的,现在回到 iotc-preview 应用程序,查看 About 结果如何更改

要查看“**Overview**”仪表板上的更改,请多次按下“**Publish**”按钮。请注意,将 JSON 遥测对象包装在数组中,将根据 config 文件中的 appsettings 配置自动生成样本。

仪表板显示了 Azure IoT Hub Tester 从即插即用 azurkit-1 设备发布的遥测数据

以上是此版本的内容。我预计很快会有新的 Microsoft 即插即用 IoT 设备更新。能够将设备功能模型存储到设备孪生中将很有用。这将允许我们根据临时需求按设备获取功能模型。好的,我们将看看这项功能会如何发展。我相信,此测试工具的下一个版本也会随之而来。

参考文献

历史

  • 2017 年 3 月 31 日:初始版本
© . All rights reserved.